[
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Code of Conduct\nThis project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).\nFor more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact\nopensource-codeofconduct@amazon.com with any additional questions or comments."
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nThank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional\ndocumentation, we greatly value feedback and contributions from our community.\n\nPlease read through this document before submitting any issues or pull requests to ensure we have all the necessary\ninformation to effectively respond to your bug report or contribution.\n\n\n## Reporting Bugs/Feature Requests\n\nWe welcome you to use the GitHub issue tracker to report bugs or suggest features.\n\nWhen filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already\nreported the issue. Please try to include as much information as you can. Details like these are incredibly useful:\n\n* A reproducible test case or series of steps\n* The version of our code being used\n* Any modifications you've made relevant to the bug\n* Anything unusual about your environment or deployment\n\n\n## Contributing via Pull Requests\nContributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:\n\n1. You are working against the latest source on the *main* branch.\n2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.\n3. You open an issue to discuss any significant work - we would hate for your time to be wasted.\n\nTo send us a pull request, please:\n\n1. Fork the repository.\n2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.\n3. Ensure local tests pass.\n4. Commit to your fork using clear commit messages.\n5. Send us a pull request, answering any default questions in the pull request interface.\n6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.\n\nGitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and\n[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).\n\n\n## Finding contributions to work on\nLooking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.\n\n\n## Code of Conduct\nThis project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).\nFor more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact\nopensource-codeofconduct@amazon.com with any additional questions or comments.\n\n\n## Security issue notifications\nIf you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.\n\n\n## Licensing\n\nSee the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution."
  },
  {
    "path": "Cloud9Setup/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Cloud9Setup/README.md",
    "content": "# Setup Cloud9\nsam build -t prereq-sam-template.yaml --use-container\nsam deploy\n\n\n"
  },
  {
    "path": "Cloud9Setup/increase-disk-size.sh",
    "content": "#!/bin/bash\n\n# Specify the desired volume size in GiB as a command line argument. If not specified, default to 50 GiB.\nSIZE=50\n\n# Get the ID of the environment host Amazon EC2 instance.\nINSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id)\nREGION=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/\\(.*\\)[a-z]/\\1/')\n\n# Get the ID of the Amazon EBS volume associated with the instance.\nVOLUMEID=$(aws ec2 describe-instances \\\n  --instance-id $INSTANCEID \\\n  --query \"Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId\" \\\n  --output text \\\n  --region $REGION)\n\n# Resize the EBS volume.\naws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE\n\n# Wait for the resize to finish.\nwhile [ \\\n  \"$(aws ec2 describe-volumes-modifications \\\n    --volume-id $VOLUMEID \\\n    --filters Name=modification-state,Values=\"optimizing\",\"completed\" \\\n    --query \"length(VolumesModifications)\"\\\n    --output text)\" != \"1\" ]; do\n    sleep 1\ndone\n\n#Check if we're on an NVMe filesystem\nif [[ -e \"/dev/xvda\" && $(readlink -f /dev/xvda) = \"/dev/xvda\" ]]\nthen\n  # Rewrite the partition table so that the partition takes up all the space that it can.\n  sudo growpart /dev/xvda 1\n\n  # Expand the size of the file system.\n  # Check if we're on AL2\n  STR=$(cat /etc/os-release)\n  SUB=\"VERSION_ID=\\\"2\\\"\"\n  if [[ \"$STR\" == *\"$SUB\"* ]]\n  then\n    sudo xfs_growfs -d /\n  else\n    sudo resize2fs /dev/xvda1\n  fi\n\nelse\n  # Rewrite the partition table so that the partition takes up all the space that it can.\n  sudo growpart /dev/nvme0n1 1\n\n  # Expand the size of the file system.\n  # Check if we're on AL2\n  STR=$(cat /etc/os-release)\n  SUB=\"VERSION_ID=\\\"2\\\"\"\n  if [[ \"$STR\" == *\"$SUB\"* ]]\n  then\n    sudo xfs_growfs -d /\n  else\n    sudo resize2fs /dev/nvme0n1p1\n  fi\nfi"
  },
  {
    "path": "Cloud9Setup/pre-requisites-versions-check.sh",
    "content": "#!/bin/bash\nSUMMARY=\"Make sure all the pre-requisites checks PASS\"$'\\n'\ncheck_version() {\n  retval=0  \n  MIN_VERSION=$1\n  CURRENT_VERSION=$2\n  IFS='.' read -r -a minarr <<< \"$MIN_VERSION\"\n  IFS='.' read -r -a currarr <<< \"$CURRENT_VERSION\"\n  \n  for ((i=0; i<${#minarr[@]}; i++));\n  do\n    #echo \"${currarr[$i]}, ${minarr[$i]}\"\n    if [[ ${currarr[$i]} -gt ${minarr[$i]} ]]; then\n        break\n    elif [[ ${currarr[$i]} -lt ${minarr[$i]} ]]; then\n        retval=1\n        break\n    else\n        continue\n    fi        \n  done\n\n  return $retval  \n}\n\necho \"Checking python version\"\npython3 --version\nPYTHON_VERSION=$(python3 --version 2>&1 | cut -d'n' -f 2 | xargs)\nPYTHON_MIN_VERSION=3.8.0\ncheck_version $PYTHON_MIN_VERSION $PYTHON_VERSION\nif [[ $? -eq 1 ]]; then\n    echo \"ACTION REQUIRED: Need to have python version greater than or equal to $PYTHON_MIN_VERSION\"\n    SUMMARY+=\"* ACTION REQUIRED: Need to have python version greater than or equal to $PYTHON_MIN_VERSION\"$'\\n'\nelse \n    SUMMARY+=\"* PASS : Python version $PYTHON_VERSION installed. The minimum required version $PYTHON_MIN_VERSION\"$'\\n'\nfi\necho \"\"\n\necho \"Checking aws cli version\"\naws --version\nif [[ $? -ne 0 ]]; then\n     echo \"ACTION REQUIRED: aws cli is missing, please install !!\" \n     SUMMARY+=\"* ACTION REQUIRED: aws cli is missing, please install !!\"$'\\n'\nelse \n    AWS_VERSION=$(aws --version | cut -d'P' -f 1 | xargs)\n    SUMMARY+=\"* PASS : $AWS_VERSION version installed\"$'\\n'\n    jq --version\n    if [[ $? -ne 0 ]]; then\n        echo yes | sudo yum install jq\n    fi    \n    CLOUD9_INSTANCE=$(aws ec2 describe-instances --filters Name=instance-type,Values=t3.large | jq -r '.Reservations[0].Instances[0].InstanceId')\n    if [ -z $CLOUD9_INSTANCE ]; then\n        echo \"ACTION REQUIRED: Looks like Cloud9 instance with t3.large instance type is missing. Please create one!!\"\n        SUMMARY+=\"* ACTION REQUIRED: Looks like Cloud9 instance with t3.large instance type is missing. Please create one!!\"$'\\n'\n    else\n        SUMMARY+=\"* PASS : Has required t3.large instance type\"$'\\n' \n        CLOUD9_INSTANCE_VOLUME_ID=$(aws ec2 describe-instances --filters Name=instance-type,Values=t3.large | jq -r '.Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId')\n        VOLUME_SIZE=$(aws ec2 describe-volumes --volume-ids $CLOUD9_INSTANCE_VOLUME_ID | jq -r '.Volumes[0].Size')\n        if [[ $VOLUME_SIZE -lt 50 ]]; then\n            echo \"ACTION REQUIRED: The volume size of cloud9 is less than 50GiB. Please update volume size to atleast 50GiB\"\n            SUMMARY+=\"* ACTION REQUIRED: The volume size of cloud9 is less than 50GiB. Please update volume size to atleast 50GiB\"$'\\n'\n        else\n            SUMMARY+=\"* PASS : Has minimum required 50GiB volume size\"$'\\n'\n        fi\n    fi\nfi\necho \"\"\n\necho \"Checking sam cli version\"\nsam --version\nSAM_VERSION=$(sam --version | cut -d'n' -f 2 | xargs)\nSAM_MIN_VERSION=1.53.0\ncheck_version $SAM_MIN_VERSION $SAM_VERSION\nif [[ $? -eq 1 ]]; then\n    echo \"ACTION REQUIRED: Need to have SAM version greater than or equal to $SAM_MIN_VERSION\"\n    SUMMARY+=\"* ACTION REQUIRED: Need to have SAM version greater than or equal to $SAM_MIN_VERSION\"$'\\n'\nelse\n    SUMMARY+=\"* PASS : Sam cli version $SAM_VERSION installed. The minimum required version $SAM_MIN_VERSION\"$'\\n'\nfi\necho \"\"\n\necho \"Checking git-remote-codecommit version\"\npython3 -m pip show git-remote-codecommit\nif [[ $? -ne 0 ]]; then\n    echo \"ACTION REQUIRED: git-remote-codecommit is missing, please install\"\n    SUMMARY+=\"* ACTION REQUIRED: git-remote-codecommit is missing, please install !!\"$'\\n'\nelse\n    SUMMARY+=\"* PASS : Has git-remote-codecommit installed\"$'\\n'\nfi\necho \"\"\n\necho \"Checking node version\"\nnode --version\nNODE_VERSION=$(node --version | cut -d'v' -f 2)\nNODE_MIN_VERSION=14.0.0\ncheck_version $NODE_MIN_VERSION $NODE_VERSION \nif [[ $? -eq 1 ]]; then\n    echo \"ACTION REQUIRED: Need to have Node version greater than or equal to $NODE_MIN_VERSION\"\n    SUMMARY+=\"* ACTION REQUIRED: Need to have Node version greater than or equal to $NODE_MIN_VERSION\"$'\\n'\nelse\n    SUMMARY+=\"* PASS : Node version $NODE_VERSION installed. The minimum required version $NODE_MIN_VERSION\"$'\\n'\nfi\necho \"\"\n\n\necho \"Checking cdk version\"\ncdk --version\nCDK_VERSION=$(cdk --version | cut -d'(' -f 1| xargs)\nCDK_MIN_VERSION=2.40.0\ncheck_version $CDK_MIN_VERSION $CDK_VERSION\nif [[ $? -eq 1 ]]; then\n    echo \"ACTION REQUIRED: Need to have CDK version greater than or equal to $CDK_MIN_VERSION\"\n    SUMMARY+=\"* ACTION REQUIRED: Need to have CDK version greater than or equal to $CDK_MIN_VERSION\"$'\\n'\nelse \n    SUMMARY+=\"* PASS : CDK version $CDK_VERSION installed. The minimum required version $CDK_MIN_VERSION\"$'\\n'\nfi\necho \"\"\n\necho \"***************SUMMARY****************\"\necho \"$SUMMARY\"\necho \"***************END OF SUMMARY*********\"\n"
  },
  {
    "path": "Cloud9Setup/pre-requisites.sh",
    "content": "#!/bin/bash -x\n\n#Installing NVM\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | sudo -u ec2-user bash\n\n. /home/ec2-user/.nvm/nvm.sh\n\n#Install python3.8\nsudo yum install -y amazon-linux-extras\nsudo amazon-linux-extras enable python3.8\nsudo yum install -y python3.8\nsudo alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1\nsudo alternatives --set python3 /usr/bin/python3.8\n\n# Uninstall aws cli v1 and Install aws cli version-2.3.0\nsudo pip uninstall awscli -y\n\necho \"Installing aws cli version-2.3.0\"\ncurl \"https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.3.0.zip\" -o \"awscliv2.zip\"\nunzip awscliv2.zip\nsudo ./aws/install\nrm awscliv2.zip\nrm -rf aws \n\n# Install sam cli version 1.64.0\necho \"Installing sam cli version 1.64.0\"\nwget https://github.com/aws/aws-sam-cli/releases/download/v1.64.0/aws-sam-cli-linux-x86_64.zip\nunzip aws-sam-cli-linux-x86_64.zip -d sam-installation\nsudo ./sam-installation/install\nif [ $? -ne 0 ]; then\n\techo \"Sam cli is already present, so deleting existing version\"\n\tsudo rm /usr/local/bin/sam\n\tsudo rm -rf /usr/local/aws-sam-cli\n\techo \"Now installing sam cli version 1.64.0\"\n\tsudo ./sam-installation/install    \nfi\nrm aws-sam-cli-linux-x86_64.zip\nrm -rf sam-installation\n\n# Install git-remote-codecommit version 1.15.1\necho \"Installing git-remote-codecommit version 1.15.1\"\ncurl -O https://bootstrap.pypa.io/get-pip.py\npython3 get-pip.py --user\nrm get-pip.py\n\npython3 -m pip install git-remote-codecommit==1.15.1\n\n# Install node v14.18.1\necho \"Installing node v14.18.1\"\nnvm deactivate\nnvm uninstall node\nnvm install v14.18.1\nnvm use v14.18.1\nnvm alias default v14.18.1\n\n# Install cdk cli version ^2.40.0\necho \"Installing cdk cli version ^2.40.0\"\nnpm uninstall -g aws-cdk\nnpm install -g aws-cdk@\"^2.40.0\"\n\n#Install jq version 1.5\nsudo yum -y install jq-1.5\n\n#Install pylint version 2.11.1\npython3 -m pip install pylint==2.11.1\n\npython3 -m pip install boto3\n"
  },
  {
    "path": "Cloud9Setup/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas-init-environment\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\""
  },
  {
    "path": "LICENSE",
    "content": "Creative Commons Attribution-ShareAlike 4.0 International Public License\n\nBy exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License (\"Public License\"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.\n\nSection 1 – Definitions.\n\t\n     a.\tAdapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.\n\t\n     b.\tAdapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.\n\t\n     c.\tBY-SA Compatible License means a license listed at creativecommons.org/compatiblelicenses, approved by Creative Commons as essentially the equivalent of this Public License.\n\t\n     d.\tCopyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.\n\t\n     e.\tEffective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.\n\t\n     f.\tExceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.\n\t\n     g.\tLicense Elements means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.\n\t\n     h.\tLicensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.\n\t\n     i.\tLicensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.\n\t\n     j.\tLicensor means the individual(s) or entity(ies) granting rights under this Public License.\n\t\n     k.\tShare means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.\n\t\n     l.\tSui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.\n\t\n     m.\tYou means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.\n\nSection 2 – Scope.\n\t\n     a.\tLicense grant.\n\t\n          1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:\n\n               A. reproduce and Share the Licensed Material, in whole or in part; and\t\n\n               B. produce, reproduce, and Share Adapted Material.\n\t\n          2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.\n\t\n          3. Term. The term of this Public License is specified in Section 6(a).\n\t\n          4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.\n\t\n          5. Downstream recipients.\n\n               A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.\n\t\n               B. Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.\n\t\n               C. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.\n\t\n          6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).\n\t\n     b.\tOther rights.\n\t\n          1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.\n\t\n          2. Patent and trademark rights are not licensed under this Public License.\n\t\n          3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.\n\nSection 3 – License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the following conditions.\n\t\n     a.\tAttribution.\n\t\n          1. If You Share the Licensed Material (including in modified form), You must:\n\n               A. retain the following if it is supplied by the Licensor with the Licensed Material:\n\n                    i.\tidentification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);\n\n                    ii.\ta copyright notice;\n\n                    iii. a notice that refers to this Public License;\n\n                    iv.\ta notice that refers to the disclaimer of warranties;\n\n                    v.\ta URI or hyperlink to the Licensed Material to the extent reasonably practicable;\n\n               B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and\n\n               C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.\n\t\n          2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.\n\t\n          3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.\n\t\n     b.\tShareAlike.In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.\n\t\n          1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.\n\t\n          2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.\n\t\n          3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.\n\nSection 4 – Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:\n\t\n     a.\tfor the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;\n\t\n     b.\tif You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and\n\t\n     c.\tYou must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.\nFor the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.\n\nSection 5 – Disclaimer of Warranties and Limitation of Liability.\n\t\n     a.\tUnless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.\n\t\n     b.\tTo the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.\n\t\n     c.\tThe disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.\n\nSection 6 – Term and Termination.\n\t\n     a.\tThis Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.\n\t\n     b.\tWhere Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:\n\t\n          1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or\n\t\n          2. upon express reinstatement by the Licensor.\n\t\n     c.\tFor the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.\n\t\n     d.\tFor the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.\n\t\n     e.\tSections 1, 5, 6, 7, and 8 survive termination of this Public License.\n\nSection 7 – Other Terms and Conditions.\n\t\n     a.\tThe Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.\n\t\n     b.\tAny arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.\n\nSection 8 – Interpretation.\n\t\n     a.\tFor the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.\n\t\n     b.\tTo the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.\n\t\n     c.\tNo term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.\n\t\n     d.\tNothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.\n"
  },
  {
    "path": "LICENSE-SAMPLECODE",
    "content": "Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this\nsoftware and associated documentation files (the \"Software\"), to deal in the Software\nwithout restriction, including without limitation the rights to use, copy, modify,\nmerge, publish, distribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "LICENSE-SUMMARY",
    "content": "Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. \n\nThe documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International License. See the LICENSE file.\n\nThe sample code within this documentation is made available under the MIT-0 license. See the LICENSE-SAMPLECODE file.\n"
  },
  {
    "path": "Lab1/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab1/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab1/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab1/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab1/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/Application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab1/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n];\n"
  },
  {
    "path": "Lab1/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: '',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab1/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'application';\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab1/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container fullscreen>\n  <mat-sidenav\n    [mode]=\"'side'\"\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <div class=\"sidebar-icon-container\">\n      <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n    </div>\n    <mat-divider></mat-divider>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <div class=\"content\" #main>\n    <router-outlet></router-outlet>\n  </div>\n  <div class=\"footer\" #footer>\n    <div class=\"footer-text\">\n      <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n      <span class=\"spacer\"></span>\n      <span>\n        Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n      </span>\n    </div>\n  </div>\n</mat-sidenav-container>\n\n"
  },
  {
    "path": "Lab1/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {}\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { environment } from 'src/environments/environment';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = environment.apiGatewayUrl;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Edit Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Product ID</mat-label>\n          <input\n            matInput\n            value=\"{{ productId$ | async }}\"\n            [readonly]=\"true\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select formControlName=\"category\" required>\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{ category }}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router,\n    private route: ActivatedRoute\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      shardId: [],\n      productId: [],\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/products/edit/{{ element.productId }}\">\n              {{ element.name }}\n            </a>\n          </td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { environment } from 'src/environments/environment';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = environment.apiGatewayUrl;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Lab1/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab1/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab1/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://0grqqki7qk.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab1/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://0grqqki7qk.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab1/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab1/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab1/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab1/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab1/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}\n"
  },
  {
    "path": "Lab1/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab1/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab1/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab1/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab1/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c --stack-name <CloudFormation stack name>\"\n  echo \"Command to deploy server code: deployment.sh -s --stack-name <CloudFormation stack name>\"\n  echo \"Command to deploy server & client code: deployment.sh -s -c --stack-name <CloudFormation stack name>\"\n  exit 1\nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n  case $1 in\n  -s) server=1 ;;\n  -c) client=1 ;;\n  --stack-name)\n    stackname=$2\n    shift\n    ;;\n  *)\n    echo \"Unknown parameter passed: $1\"\n    exit 1\n    ;;\n  esac\n  shift\ndone\n\nif [[ -z \"$stackname\" ]]; then\n  echo \"Please provide CloudFormation stack name as parameter\"\n  echo \"Note: Invoke script without parameters to know the list of script parameters\"\n  exit 1\nfi\n\nif [[ $server -eq 1 ]]; then\n  echo \"Server code is getting deployed\"\n  cd ../server || exit # stop execution if cd fails\n  REGION=$(aws configure get region)\n\n  DEFAULT_SAM_S3_BUCKET=$(grep s3_bucket samconfig.toml | cut -d'=' -f2 | cut -d \\\" -f2)\n  echo \"aws s3 ls s3://$DEFAULT_SAM_S3_BUCKET\"\n\n  if ! aws s3 ls \"s3://${DEFAULT_SAM_S3_BUCKET}\"; then\n    echo \"S3 Bucket: $DEFAULT_SAM_S3_BUCKET specified in samconfig.toml is not readable.\n      So creating a new S3 bucket and will update samconfig.toml with new bucket name.\"\n\n    UUID=$(uuidgen | awk '{print tolower($0)}')\n    SAM_S3_BUCKET=sam-bootstrap-bucket-$UUID\n    aws s3 mb \"s3://${SAM_S3_BUCKET}\" --region \"$REGION\"\n    aws s3api put-bucket-encryption \\\n      --bucket \"$SAM_S3_BUCKET\" \\\n      --server-side-encryption-configuration '{\"Rules\": [{\"ApplyServerSideEncryptionByDefault\": {\"SSEAlgorithm\": \"AES256\"}}]}'\n    if [[ $? -ne 0 ]]; then\n      exit 1\n    fi\n    # Updating samconfig.toml with new bucket name\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' samconfig.toml\n  fi\n\n  echo \"Validating server code using pylint\"\n  python3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n\n  sam build -t template.yaml --use-container\n  sam deploy --config-file samconfig.toml --region=\"$REGION\" --stack-name=\"$stackname\"\n  cd ../scripts || exit # stop execution if cd fails\nfi\n\nif [[ $client -eq 1 ]]; then\n  echo \"Client code is getting deployed\"\n  APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='AppBucket'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\n  APP_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='APIGatewayURL'].OutputValue\" --output text)\n\n  # Configuring application UI\n\n  echo \"aws s3 ls s3://${APP_SITE_BUCKET}\"\n  if ! aws s3 ls \"s3://${APP_SITE_BUCKET}\"; then\n    echo \"Error! S3 Bucket: $APP_SITE_BUCKET not readable\"\n    exit 1\n  fi\n\n  cd ../client/Application || exit # stop execution if cd fails\n\n  echo \"Configuring environment for App Client\"\n\n  cat <<EoF >./src/environments/environment.prod.ts\nexport const environment = {\n  production: true,\n  apiGatewayUrl: '$APP_APIGATEWAYURL'\n};\nEoF\n\n  cat <<EoF >./src/environments/environment.ts\nexport const environment = {\n  production: true,\n  apiGatewayUrl: '$APP_APIGATEWAYURL'\n};\nEoF\n\n  npm install && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://${APP_SITE_BUCKET}\"\n  if ! aws s3 sync --delete --cache-control no-store dist \"s3://${APP_SITE_BUCKET}\"; then\n    exit 1\n  fi\n\n  echo \"Completed configuring environment for App Client\"\n\n  echo \"Application site URL: https://${APP_SITE_URL}\"\nfi\n"
  },
  {
    "path": "Lab1/scripts/geturl.sh",
    "content": "#!/bin/bash\nstackname=\"serverless-saas-workshop-lab1\"\n\nAPP_SITE_URL=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\necho \"Application site URL: https://${APP_SITE_URL}\"\n"
  },
  {
    "path": "Lab1/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Lab1/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, orderId, orderName, orderProducts):\n        self.orderId = orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Lab1/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\n\ndef get_order(event, context):\n    logger.info(\"Request received to get a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.get_order(event, orderId)\n\n    logger.info(\"Request completed to get a order\")\n    \n    return utils.generate_response(order)\n    \ndef create_order(event, context):  \n    logger.info(\"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.info(\"Request completed to create a order\")\n    return utils.generate_response(order)\n    \ndef update_order(event, context):    \n    logger.info(\"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.update_order(event, payload, orderId)\n    logger.info(\"Request completed to update a order\")     \n    return utils.generate_response(order)\n\ndef delete_order(event, context):\n    logger.info(\"Request received to delete a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    response = order_service_dal.delete_order(event, orderId)\n    logger.info(\"Request completed to delete a order\")\n    return utils.create_success_response(\"Successfully deleted the order\")\n\ndef get_orders(event, context):\n    logger.info(\"Request received to get all orders\")\n    response = order_service_dal.get_orders(event)\n    logger.info(\"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab1/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\n\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_order(event, orderId):\n    \n    try:\n        response = table.get_item(Key={'orderId': orderId})\n        item = response['Item']\n        order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, orderId):\n    \n    try:\n        response = table.delete_item(Key={'orderId': orderId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    \n    order = Order(str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        })\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, orderId):\n    \n    try:\n        order = Order(orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event):\n    orders = []\n\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n                orders.append(order)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return orders\n\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Lab1/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab1/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, productId, sku, name, price, category):\n        self.productId = productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Lab1/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport product_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\n\ndef get_product(event, context):\n    logger.info(\"Request received to get a product\")\n    params = event['pathParameters']\n    productId = params['id']\n    product = product_service_dal.get_product(event, productId)\n    logger.info(\"Request completed to get a product\")    \n    return utils.generate_response(product)\n    \ndef create_product(event, context):    \n    logger.info(\"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    logger.info(payload)\n    product = product_service_dal.create_product(event, payload)\n    logger.info(\"Request completed to create a product\")\n    return utils.generate_response(product)\n    \ndef update_product(event, context):\n    logger.info(\"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.info(\"Request completed to update a product\") \n    return utils.generate_response(product)\n\ndef delete_product(event, context):\n    logger.info(\"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.info(\"Request completed to delete a product\")\n    return utils.create_success_response(\"Successfully deleted the product\")\n\ndef get_products(event, context):\n    logger.info(\"Request received to get all products\")\n    response = product_service_dal.get_products(event)\n    logger.info(\"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab1/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport logger\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_product(event, productId):\n    try:\n        response = table.get_item(Key={'productId': productId})\n        item = response['Item']\n        product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, productId):\n    try:\n        response = table.delete_item(Key={'productId': productId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    product = Product(str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }\n        )\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, productId):\n    try:\n        product = Product(productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event):    \n    products =[]\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n                products.append(product)\n                    \n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return products\n\n\n\n"
  },
  {
    "path": "Lab1/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab1/server/README.md",
    "content": "if using EE\nsam build --use-container && sam package  --output-template-file packaged.yaml --s3-bucket aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx --region us-west-2\nsam deploy --template-file packaged.yaml --config-file samconfig.toml\n\n\nNormally\nsam build -t template.yaml --use-container\nsam deploy --config-file samconfig.toml\n\n"
  },
  {
    "path": "Lab1/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n"
  },
  {
    "path": "Lab1/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\n"
  },
  {
    "path": "Lab1/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\n\n\n\n"
  },
  {
    "path": "Lab1/server/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas-lab1\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas-lab1\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab1/server/template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Lab1 - Basic Serverless Application\n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG                   \n\nParameters:\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\n    \nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-workshoplab1\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: productId\n          KeyType: HASH  \n      BillingMode: PAY_PER_REQUEST \n      TableName: Product-Lab1\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: orderId\n          KeyType: HASH  \n      BillingMode: PAY_PER_REQUEST\n      TableName: Order-Lab1\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: product-function-execution-role-lab1\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole        \n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: product-function-policy-lab1\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                  - dynamodb:Scan\n                Resource:\n                  - !GetAtt ProductTable.Arn\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  \n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      \n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n  \n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n          \n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: order-function-execution-role-lab1\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole       \n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess \n      Policies:\n        - PolicyName: order-function-policy-lab1\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                  - dynamodb:Scan\n                Resource:\n                  - !GetAtt OrderTable.Arn\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-workshop-lab1-api\n      RetentionInDays: 30\n  ApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: 'serverless-saas-workshop-lab1'\n        basePath: !Join ['', ['/', !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}              \n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                            \n      StageName: !Ref StageName            \n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]            \n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distribution\"\n  AppBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: lab1-tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: lab1-tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'         \nOutputs:\n  APIGatewayURL:\n    Description: \"API Gateway endpoint URL for API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName  \n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket  "
  },
  {
    "path": "Lab2/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab2/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab2/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab2/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab2/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab2/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab2/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab2/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab2/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Lab2/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab2/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab2/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab2/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab2/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab2/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab2/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab2/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Lab2/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab2/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab2/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab2/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab2/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab2/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab2/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab2/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab2/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab2/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab2/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab2/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab2/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab2/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab2/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab2/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab2/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab2/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab2/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Lab2/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab2/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab2/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab2/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab2/scripts/deploy-updates.sh",
    "content": "#!/bin/bash\ncd ../server || exit # stop execution if cd fails\nrm -rf .aws-sam/\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n#Deploying shared services changes\necho \"Deploying shared services changes\" \necho Y | sam sync --stack-name serverless-saas --code --resource-id LambdaFunctions/CreateUserFunction --resource-id LambdaFunctions/RegisterTenantFunction --resource-id LambdaFunctions/GetTenantFunction -u\n\ncd ../scripts || exit\n./geturl.sh"
  },
  {
    "path": "Lab2/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c --email <email address>\"\n  echo \"Command to deploy server code: deployment.sh -s --email <email address>\"\n  echo \"Command to deploy server & client code: deployment.sh -s -c --email <email address>\"\n  exit 1\nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n  case $1 in\n  -s) server=1 ;;\n  -c) client=1 ;;\n  --email)\n    email=$2\n    shift\n    ;;\n  *)\n    echo \"Unknown parameter passed: $1\"\n    exit 1\n    ;;\n  esac\n  shift\ndone\n\n# During AWS hosted events using event engine tool\n# we pre-provision cloudfront and s3 buckets which hosts UI code.\n# So that it improves this labs total execution time.\n# Below code checks if cloudfront and s3 buckets are\n# pre-provisioned or not and then concludes if the workshop\n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false\nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  ADMIN_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminSiteBucket'].Value\" --output text)\n  LANDING_APP_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSiteBucket'].Value\" --output text)\nfi\n\nif [[ $server -eq 1 ]]; then\n  echo \"Server code is getting deployed\"\n\n  cd ../server || exit # stop execution if cd fails\n  REGION=$(aws configure get region)\n  DEFAULT_SAM_S3_BUCKET=$(grep s3_bucket samconfig.toml | cut -d'=' -f2 | cut -d \\\" -f2)\n  echo \"aws s3 ls s3://$DEFAULT_SAM_S3_BUCKET\"\n  if ! aws s3 ls \"s3://${DEFAULT_SAM_S3_BUCKET}\"; then\n    echo \"S3 Bucket: $DEFAULT_SAM_S3_BUCKET specified in samconfig.toml is not readable.\n      So creating a new S3 bucket and will update samconfig.toml with new bucket name.\"\n\n    UUID=$(uuidgen | awk '{print tolower($0)}')\n    SAM_S3_BUCKET=sam-bootstrap-bucket-$UUID\n    aws s3 mb \"s3://${SAM_S3_BUCKET}\" --region \"$REGION\"\n    aws s3api put-bucket-encryption \\\n      --bucket \"$SAM_S3_BUCKET\" \\\n      --server-side-encryption-configuration '{\"Rules\": [{\"ApplyServerSideEncryptionByDefault\": {\"SSEAlgorithm\": \"AES256\"}}]}'\n    if [[ $? -ne 0 ]]; then\n      exit 1\n    fi\n    # Updating all labs samconfig.toml with new bucket name\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab3/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab3/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab4/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab4/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab5/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab5/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab6/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab6/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab7/samconfig.toml\n  fi\n\n  echo \"Validating server code using pylint\"\n  python3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n\n  sam build -t template.yaml --use-container\n\n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file samconfig.toml --region=\"$REGION\" --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL\n  else\n    sam deploy --config-file samconfig.toml --region=\"$REGION\" --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n\n  cd ../scripts || exit # stop execution if cd fails\nfi\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  ADMIN_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminSiteBucket'].OutputValue\" --output text)\n  LANDING_APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSiteBucket'].OutputValue\" --output text)\nfi\n\nif [[ $client -eq 1 ]]; then\n  if [[ -z \"$email\" ]]; then\n    echo \"Please provide email address to setup an admin user\"\n    echo \"Note: Invoke script without parameters to know the list of script parameters\"\n    exit 1\n  fi\n  echo \"Client code is getting deployed\"\n\n  ADMIN_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminApi'].OutputValue\" --output text)\n  ADMIN_APPCLIENTID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoOperationUsersUserPoolClientId'].OutputValue\" --output text)\n  ADMIN_USERPOOL_ID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoOperationUsersUserPoolId'].OutputValue\" --output text)\n  ADMIN_USER_GROUP_NAME=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoAdminUserGroupName'].OutputValue\" --output text)\n\n  # Create admin-user in OperationUsers userpool with given input email address\n  CREATE_ADMIN_USER=$(aws cognito-idp admin-create-user \\\n  --user-pool-id \"$ADMIN_USERPOOL_ID\" \\\n  --username admin-user \\\n  --user-attributes Name=email,Value=\"$email\" Name=email_verified,Value=\"True\" Name=phone_number,Value=\"+11234567890\" Name=\"custom:userRole\",Value=\"SystemAdmin\" Name=\"custom:tenantId\",Value=\"system_admins\" \\\n  --desired-delivery-mediums EMAIL)\n\n  echo \"$CREATE_ADMIN_USER\"\n\n  # Add admin-user to admin user group\n  ADD_ADMIN_USER_TO_GROUP=$(aws cognito-idp admin-add-user-to-group \\\n    --user-pool-id \"$ADMIN_USERPOOL_ID\" \\\n    --username admin-user \\\n    --group-name \"$ADMIN_USER_GROUP_NAME\")\n\n  echo \"$ADD_ADMIN_USER_TO_GROUP\"\n\n  # Configuring admin UI\n\n  echo \"aws s3 ls s3://$ADMIN_SITE_BUCKET\"\n  if ! aws s3 ls \"s3://${ADMIN_SITE_BUCKET}\"; then\n    echo \"Error! S3 Bucket: $ADMIN_SITE_BUCKET not readable\"\n    exit 1\n  fi\n\n  cd ../client/Admin || exit # stop execution if cd fails\n\n  echo \"Configuring environment for Admin Client\"\n  cat <<EoF >./src/environments/environment.prod.ts\nexport const environment = {\n  production: true,\n  apiUrl: '$ADMIN_APIGATEWAYURL',\n};\nEoF\n\n  cat <<EoF >./src/environments/environment.ts\nexport const environment = {\n  production: false,\n  apiUrl: '$ADMIN_APIGATEWAYURL',\n};\nEoF\n\n  cat <<EoF >./src/aws-exports.ts\nconst awsmobile = {\n    \"aws_project_region\": \"$REGION\",\n    \"aws_cognito_region\": \"$REGION\",\n    \"aws_user_pools_id\": \"$ADMIN_USERPOOL_ID\",\n    \"aws_user_pools_web_client_id\": \"$ADMIN_APPCLIENTID\",\n};\n\nexport default awsmobile;\nEoF\n\n  npm install && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://${ADMIN_SITE_BUCKET}\"\n  aws s3 sync --delete --cache-control no-store dist \"s3://${ADMIN_SITE_BUCKET}\"\n\n  if [[ $? -ne 0 ]]; then\n    exit 1\n  fi\n\n  echo \"Completed configuring environment for Admin Client\"\n\n  # Configuring landing UI\n\n  echo \"aws s3 ls s3://${LANDING_APP_SITE_BUCKET}\"\n  if ! aws s3 ls \"s3://${LANDING_APP_SITE_BUCKET}\"; then\n    echo \"Error! S3 Bucket: $LANDING_APP_SITE_BUCKET not readable\"\n    exit 1\n  fi\n\n  cd ../\n\n  cd Landing || exit # stop execution if cd fails\n\n  echo \"Configuring environment for Landing Client\"\n\n  cat <<EoF >./src/environments/environment.prod.ts\nexport const environment = {\n  production: true,\n  apiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n};\nEoF\n  cat <<EoF >./src/environments/environment.ts\nexport const environment = {\n  production: false,\n  apiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n};\nEoF\n\n  npm install && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://${LANDING_APP_SITE_BUCKET}\"\n  aws s3 sync --delete --cache-control no-store dist \"s3://${LANDING_APP_SITE_BUCKET}\"\n\n  if [[ $? -ne 0 ]]; then\n    exit 1\n  fi\n\n  echo \"Completed configuring environment for Landing Client\"\n  echo \"Successfully completed deploying Admin UI and Landing UI\"\nfi\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\n"
  },
  {
    "path": "Lab2/scripts/geturl.sh",
    "content": "#!/bin/bash\nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\n"
  },
  {
    "path": "Lab2/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Lab2/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, orderId, orderName, orderProducts):\n        self.orderId = orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Lab2/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\n\ndef get_order(event, context):\n    logger.info(\"Request received to get a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.get_order(event, orderId)\n\n    logger.info(\"Request completed to get a order\")\n    \n    return utils.generate_response(order)\n    \ndef create_order(event, context):  \n    logger.info(\"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.info(\"Request completed to create a order\")\n    return utils.generate_response(order)\n    \ndef update_order(event, context):    \n    logger.info(\"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.update_order(event, payload, orderId)\n    logger.info(\"Request completed to update a order\")     \n    return utils.generate_response(order)\n\ndef delete_order(event, context):\n    logger.info(\"Request received to delete a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    response = order_service_dal.delete_order(event, orderId)\n    logger.info(\"Request completed to delete a order\")\n    return utils.create_success_response(\"Successfully deleted the order\")\n\ndef get_orders(event, context):\n    logger.info(\"Request received to get all orders\")\n    response = order_service_dal.get_orders(event)\n    logger.info(\"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab2/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\n\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_order(event, orderId):\n    \n    try:\n        response = table.get_item(Key={'orderId': orderId})\n        item = response['Item']\n        order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, orderId):\n    \n    try:\n        response = table.delete_item(Key={'orderId': orderId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    \n    order = Order(str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        })\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, orderId):\n    \n    try:\n        order = Order(orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event):\n    orders = []\n\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n                orders.append(order)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return orders\n\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Lab2/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab2/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, productId, sku, name, price, category):\n        self.productId = productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Lab2/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport product_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\n\ndef get_product(event, context):\n    logger.info(\"Request received to get a product\")\n    params = event['pathParameters']\n    productId = params['id']\n    product = product_service_dal.get_product(event, productId)\n    logger.info(\"Request completed to get a product\")    \n    return utils.generate_response(product)\n    \ndef create_product(event, context):    \n    logger.info(\"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    logger.info(payload)\n    product = product_service_dal.create_product(event, payload)\n    logger.info(\"Request completed to create a product\")\n    return utils.generate_response(product)\n    \ndef update_product(event, context):\n    logger.info(\"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.info(\"Request completed to update a product\") \n    return utils.generate_response(product)\n\ndef delete_product(event, context):\n    logger.info(\"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.info(\"Request completed to delete a product\")\n    return utils.create_success_response(\"Successfully deleted the product\")\n\ndef get_products(event, context):\n    logger.info(\"Request received to get all products\")\n    response = product_service_dal.get_products(event)\n    logger.info(\"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab2/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport logger\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_product(event, productId):\n    try:\n        response = table.get_item(Key={'productId': productId})\n        item = response['Item']\n        product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, productId):\n    try:\n        response = table.delete_item(Key={'productId': productId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    product = Product(str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }\n        )\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, productId):\n    try:\n        product = Product(productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event):    \n    products =[]\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n                products.append(product)\n                    \n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return products\n\n\n\n"
  },
  {
    "path": "Lab2/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab2/server/README.md",
    "content": "if using EE\nsam build --use-container && sam package  --output-template-file packaged.yaml --s3-bucket aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx --region us-west-2\nsam deploy --template-file packaged.yaml --config-file samconfig.toml\n\n\nNormally\nsam build -t template.yaml --use-container\nsam deploy --config-file samconfig.toml"
  },
  {
    "path": "Lab2/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Lab2/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    userpool_id = user_pool_operation_user\n    appclient_id = app_client_operation_user  \n   \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    policy.allowAllMethods()\n    \n    authResponse = policy.build()\n \n    context = {\n        'userName': user_name,\n        'userPoolId': userpool_id\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab2/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Lab2/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport requests\n\nregion = os.environ['AWS_REGION']\n\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\n#This method has been locked down to be only called from tenant registration service\ndef create_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n\n    try:          \n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],                    \n                    'isActive': True                    \n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\ndef update_tenant(event, context):\n    \n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    \n    logger.info(\"Request received to update tenant\")\n    \n    response_update = table_tenant_details.update_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n        ExpressionAttributeValues={\n                ':tenantName' : tenant_details['tenantName'],\n                ':tenantAddress': tenant_details['tenantAddress'],\n                ':tenantEmail': tenant_details['tenantEmail'],\n                ':tenantPhone': tenant_details['tenantPhone'],\n                ':tenantTier': tenant_details['tenantTier']                \n            },\n        ReturnValues=\"UPDATED_NEW\"\n        )             \n                \n    logger.info(response_update)     \n\n    logger.info(\"Request completed to update tenant\")\n    return utils.create_success_response(\"Tenant Updated\")    \n\n#TODO: Implement the below method\ndef get_tenant(event, context):\n    pass\n\ndef deactivate_tenant(event, context):\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    tenant_id = event['pathParameters']['tenantid']\n    \n    logger.info(\"Request received to deactivate tenant\")\n\n    response = table_tenant_details.update_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        UpdateExpression=\"set isActive = :isActive\",\n        ExpressionAttributeValues={\n                ':isActive': False\n            },\n        ReturnValues=\"ALL_NEW\"\n    )             \n    \n    logger.info(response)\n\n    update_user_response = __invoke_disable_users(headers, auth, host, stage_name, url_disable_users, tenant_id)\n    logger.info(update_user_response)\n\n    logger.info(\"Request completed to deactivate tenant\")\n    return utils.create_success_response(\"Tenant Deactivated\")\n\ndef activate_tenant(event, context):\n    \n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    tenant_id = event['pathParameters']['tenantid']\n    \n    logger.info(\"Request received to activate tenant\")\n\n    response = table_tenant_details.update_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        UpdateExpression=\"set isActive = :isActive\",\n        ExpressionAttributeValues={\n                ':isActive': True\n            },\n        ReturnValues=\"ALL_NEW\"\n    )             \n    \n    logger.info(response)\n\n    update_user_response = __invoke_enable_users(headers, auth, host, stage_name, url_enable_users, tenant_id)\n    logger.info(update_user_response)\n\n    logger.info(\"Request completed to activate tenant\")\n    return utils.create_success_response(\"Tenant activated\")\n    \ndef __invoke_disable_users(headers, auth, host, stage_name, invoke_url, tenant_id):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/', tenant_id])\n        response = requests.put(url, auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_enable_users(headers, auth, host, stage_name, invoke_url, tenant_id):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/', tenant_id])\n        response = requests.put(url, auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Lab2/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\n\nlambda_client = boto3.client('lambda')\n\n#TODO: Implement this method\ndef register_tenant(event, context):\n    pass\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Lab2/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport utils\nfrom boto3.dynamodb.conditions import Key\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\nuser_pool_id = os.environ['TENANT_USER_POOL_ID']\n\ndef create_tenant_admin_user(event, context):\n    logger.info(event)\n    app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n#only tenant admin can create users\n#TODO: Implement the below method\ndef create_user(event, context):\n    pass\n\ndef get_users(event, context):\n    users = []  \n    logger.info(\"Request received to get users\")\n    logger.info(event) \n    \n    response = client.list_users(\n        UserPoolId=user_pool_id\n    )\n    logger.info(response) \n    num_of_users = len(response['Users'])\n    if (num_of_users > 0):\n        for user in response['Users']:\n            user_info = UserInfo()\n            for attr in user[\"Attributes\"]:\n                if(attr[\"Name\"] == \"custom:tenantId\"):\n                    user_info.tenant_id = attr[\"Value\"]\n\n                if(attr[\"Name\"] == \"custom:userRole\"):\n                    user_info.user_role = attr[\"Value\"]    \n\n                if(attr[\"Name\"] == \"email\"):\n                    user_info.email = attr[\"Value\"] \n            user_info.enabled = user[\"Enabled\"]\n            user_info.created = user[\"UserCreateDate\"]\n            user_info.modified = user[\"UserLastModifiedDate\"]\n            user_info.status = user[\"UserStatus\"] \n            user_info.user_name = user[\"Username\"]\n            users.append(user_info)                    \n    \n    # return an empty list when there are no users otherwise will result in API Gateway error\n    return utils.generate_response(users)\n    \n\ndef get_user(event, context):\n    user_name = event['pathParameters']['username']  \n\n    logger.info(\"Request received to get user\")\n    \n    user_info = get_user_info(user_pool_id, user_name)\n    logger.info(\"Request completed to get new user \")\n    return utils.create_success_response(user_info.__dict__)\n\ndef update_user(event, context):\n    user_details = json.loads(event['body'])\n    user_name = event['pathParameters']['username']    \n\n    logger.info(\"Request received to update user\")\n\n    response = client.admin_update_user_attributes(\n        Username=user_name,\n        UserPoolId=user_pool_id,\n        UserAttributes=[\n            {\n                'Name': 'email',\n                'Value': user_details['userEmail']\n            },\n            {\n                'Name': 'custom:userRole',\n                'Value': user_details['userRole'] \n            }\n        ]\n    )\n    logger.info(response)\n\n    logger.info(\"Request completed to update user\")\n        \n    return utils.create_success_response(\"user updated\")    \n\ndef disable_user(event, context):\n    user_name = event['pathParameters']['username']\n\n    logger.info(\"Request received to disable new user\")\n    \n    response = client.admin_disable_user(\n        Username=user_name,\n        UserPoolId=user_pool_id\n    )\n        \n    logger.info(response)\n    logger.info(\"Request completed to disable new user\")\n    return utils.create_success_response(\"User disabled\")\n    \n#This method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    logger.info(event)    \n    \n    tenantid_to_update = event['tenantid']\n    \n    filtering_exp = Key('tenantId').eq(tenantid_to_update)\n    response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n    users = response.get('Items')\n    \n    for user in users:\n        response = client.admin_disable_user(\n            Username=user['userName'],\n            UserPoolId=user_pool_id\n        )\n        \n    logger.info(response)\n    logger.info(\"Request completed to disable users\")\n    return utils.create_success_response(\"Users disabled\")\n    \n\n#This method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    logger.info(event)    \n    \n    tenantid_to_update = event['tenantid']\n    \n    filtering_exp = Key('tenantId').eq(tenantid_to_update)\n    response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n    users = response.get('Items')\n    \n    for user in users:\n        response = client.admin_enable_user(\n            Username=user['userName'],\n            UserPoolId=user_pool_id\n        )\n        \n    logger.info(response)\n    logger.info(\"Request completed to enable users\")\n    return utils.create_success_response(\"Users enables\")\n\ndef get_user_info(user_pool_id, user_name):\n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.info(response)\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.info(user_info)        \n    return user_info    \n\nclass UserManagement:\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Lab2/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n"
  },
  {
    "path": "Lab2/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab2/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth\n\ndef get_headers(event):\n    return event['headers']\n"
  },
  {
    "path": "Lab2/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn            \n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable/{tenantid}:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                requestTemplates:\n                  application/json: \"{\\\"tenantid\\\": \\\"$input.params('tenantid')\\\" }\"\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable/{tenantid}:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                requestTemplates:\n                  application/json: \"{ \\\"tenantid\\\": \\\"$input.params('tenantid')\\\" }\"\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n  \nOutputs:  \n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Lab2/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  \n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  "
  },
  {
    "path": "Lab2/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\"  \nResources:  \n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Thanks for signing up. \"\n              - \"You username is {username} and temporary password is {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - https://example.com\n      LogoutURLs:\n        - https://example.com\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:  \n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL\n  CognitoAdminUserGroupName:\n    Value: !Ref CognitoAdminUserGroup"
  },
  {
    "path": "Lab2/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String  \n  TenantDetailsTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String  \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  \n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  \n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId          \n          \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"\n      \n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"   \n      \n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n      \n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n      \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n      \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n      \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']]               \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n      \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n  \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"          \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n   \n  \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  "
  },
  {
    "path": "Lab2/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST \n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE              \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL          \nOutputs:\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Lab2/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]     \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal: \n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal: \n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'    \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine  \n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName\n    Condition: IsNotRunningInEventEngine\n         "
  },
  {
    "path": "Lab2/server/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab2/server/template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Lab2 - Bootstrap common resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]    \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n  CognitoOperationUsersUserPoolId:\n    Description: The user pool id of Admin Management userpool \n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n  CognitoAdminUserGroupName:\n    Description: The Admin Management userpool admin user group name\n    Value: !GetAtt Cognito.Outputs.CognitoAdminUserGroupName    \n    "
  },
  {
    "path": "Lab3/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab3/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab3/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab3/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab3/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab3/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab3/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab3/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab3/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Lab3/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab3/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab3/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab3/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab3/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab3/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab3/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab3/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Lab3/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab3/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab3/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab3/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab3/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab3/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab3/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab3/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Lab3/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Lab3/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Lab3/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Lab3/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Lab3/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab3/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab3/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab3/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab3/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab3/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle *ngIf=\"tenantNameRequired\">Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\" *ngIf=\"tenantNameRequired\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid && tenantNameRequired\"\n              >\n                <div *ngIf=\"tenantNameRequired; else loginTextBlock\">Submit</div>\n                <ng-template #loginTextBlock>Login</ng-template>\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n  tenantNameRequired: boolean = true;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {\n    if (\n      'userPoolId' in environment &&\n      'appClientId' in environment &&\n      'apiGatewayUrl' in environment\n    ) {\n      // If a tenant's cognito configuration is provided in the\n      // \"environment\" object, then we take that instead of asking\n      // the visitor to provide the name of their tenant in order\n      // to do a look-up for that tenant's cognito configuration.\n      localStorage.setItem('tenantName', 'PooledTenants');\n      localStorage.setItem('userPoolId', (environment as any).userPoolId);\n      localStorage.setItem('appClientId', (environment as any).appClientId);\n      localStorage.setItem('apiGatewayUrl', (environment as any).apiGatewayUrl);\n      this.tenantNameRequired = false;\n    }\n  }\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    if (!this.tenantNameRequired) {\n      this.router.navigate(['/dashboard']);\n      return true;\n    }\n\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.shardId }}:{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Edit Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Product ID</mat-label>\n          <input\n            matInput\n            value=\"{{ productId$ | async }}\"\n            [readonly]=\"true\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select formControlName=\"category\" required>\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{ category }}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router,\n    private route: ActivatedRoute\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      shardId: [],\n      productId: [],\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/products/edit/{{ element.shardId }}:{{ element.productId }}\">\n              {{ element.name }}\n            </a>\n          </td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab3/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab3/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab3/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Lab3/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Lab3/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab3/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab3/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab3/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab3/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Lab3/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab3/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab3/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab3/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab3/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab3/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab3/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab3/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab3/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab3/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab3/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab3/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab3/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab3/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab3/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab3/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab3/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab3/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab3/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Lab3/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab3/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab3/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab3/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab3/client/dummy.txt",
    "content": ""
  },
  {
    "path": "Lab3/scripts/deploy-updates.sh",
    "content": "#!/bin/bash\ncd ../server || exit # stop execution if cd fails\nrm -rf .aws-sam/\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n#Deploying shared services changes\necho \"Deploying shared services changes\"  \necho Y | sam sync --stack-name serverless-saas -t shared-template.yaml --code --resource-id LambdaFunctions/ServerlessSaaSLayers --resource-id LambdaFunctions/SharedServicesAuthorizerFunction -u\n\n#Deploying tenant services changes\necho \"Deploying tenant services changes\"\nrm -rf .aws-sam/\necho Y | sam sync --stack-name stack-pooled -t tenant-template.yaml --code --resource-id ServerlessSaaSLayers --resource-id BusinessServicesAuthorizerFunction --resource-id CreateProductFunction -u\n\ncd ../scripts || exit\n./geturl.sh"
  },
  {
    "path": "Lab3/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c\"\n  echo \"Command to deploy bootstrap server code: deployment.sh -b\"\n  echo \"Command to deploy tenant server code: deployment.sh -t\"\n  echo \"Command to deploy bootstrap & tenant server code: deployment.sh -s\" \n  echo \"Command to deploy server & client code: deployment.sh -s -c\"\n  exit 1      \nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        -s) server=1 ;;\n        -b) bootstrap=1 ;;\n        -t) tenant=1 ;;\n        -c) client=1 ;;\n        *) echo \"Unknown parameter passed: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSiteBucket'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Validating server code using pylint\"\n  cd ../server\n  python3 -m pylint -E -d E0401,E1111 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n  cd ../scripts\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]]; then\n  echo \"Bootstrap server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t shared-template.yaml --use-container\n  \n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\n  else\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n  cd ../scripts\nfi  \n\nif [[ $server -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Tenant server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t tenant-template.yaml --use-container\n  sam deploy --config-file tenant-samconfig.toml --region=$REGION\n  cd ../scripts\nfi\n\n\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSiteBucket'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\nif [[ $client -eq 1 ]]; then\n  echo \"Client code is getting deployed\"\n  ADMIN_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminApi'].OutputValue\" --output text)\n  APP_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name stack-pooled --query \"Stacks[0].Outputs[?OutputKey=='TenantAPI'].OutputValue\" --output text)\n  APP_APPCLIENTID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoTenantAppClientId'].OutputValue\" --output text)\n  APP_USERPOOLID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoTenantUserPoolId'].OutputValue\" --output text)\n\n\n  # Admin UI and Landing UI are configured in Lab2 \n  echo \"Admin UI and Landing UI are configured in Lab2. Only App UI will be configured in this Lab3.\"\n  # Configuring app UI \n\n  echo \"aws s3 ls s3://$APP_SITE_BUCKET\"\n  aws s3 ls s3://$APP_SITE_BUCKET \n  if [ $? -ne 0 ]; then\n      echo \"Error! S3 Bucket: $APP_SITE_BUCKET not readable\"\n      exit 1\n  fi\n\n  cd ../client/Application\n\n  echo \"Configuring environment for App Client\"\n\n  cat << EoF > ./src/environments/environment.prod.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL',\n    apiGatewayUrl: '$APP_APIGATEWAYURL',\n    userPoolId: '$APP_USERPOOLID',\n    appClientId: '$APP_APPCLIENTID',\n  };\nEoF\n  cat << EoF > ./src/environments/environment.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL',\n    apiGatewayUrl: '$APP_APIGATEWAYURL',\n    userPoolId: '$APP_USERPOOLID',\n    appClientId: '$APP_APPCLIENTID',\n  };\nEoF\n\n  npm install --legacy-peer-deps && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET\"\n  aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET \n\n  if [[ $? -ne 0 ]]; then\n      exit 1\n  fi\n\n  echo \"Completed configuring environment for App Client\"\n  echo \"Successfully completed deploying Application UI\"\nfi  \n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\"\n"
  },
  {
    "path": "Lab3/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Lab3/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Lab3/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Lab3/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab3/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nimport metrics_manager\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\n\ntable_name = os.environ['ORDER_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\nsuffix_start = 1 \nsuffix_end = 10\n \ndef get_order(event, key):\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Lab3/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab3/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Lab3/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom decimal import Decimal\nfrom aws_lambda_powertools import Tracer\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    \n    #TODO: Capture metrics to denote that one product was created by tenant\n\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab3/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport random\nimport threading\nimport metrics_manager\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n#TODO: Implement this method\ndef create_product(event, payload):\n    pass\n\ndef update_product(event, payload, key):    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):    \n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n"
  },
  {
    "path": "Lab3/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab3/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml"
  },
  {
    "path": "Lab3/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Lab3/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport utils\nimport auth_manager\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\ntenant_userpool_id = os.environ['TENANT_USER_POOL']\ntenant_appclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        userpool_id = tenant_userpool_id\n        appclient_id = tenant_appclient_id \n    \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    #TODO: Add policy so that only tenant and SaaS admins can add/modify tenant information\n    \n\n    authResponse = policy.build()\n \n    context = {\n        'userName': user_name,\n        'userPoolId': userpool_id,\n        'tenantId': tenant_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab3/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\n\nuserpool_id = os.environ['TENANT_USER_POOL']\nappclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    # TODO: Add tenant context to authResponse\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab3/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Lab3/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport requests\nimport metrics_manager\nimport auth_manager\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nregion = os.environ['AWS_REGION']\n\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\n#This method has been locked down to be only called from tenant registration service\ndef create_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n\n    try:          \n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],                    \n                    'isActive': True                    \n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        \n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier']\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()   \n    \ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Lab3/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\n\nlambda_client = boto3.client('lambda')\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n\n        tenant_details['tenantId'] = tenant_id\n\n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Lab3/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport metrics_manager\nimport auth_manager\nimport utils\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\nuser_pool_id = os.environ['TENANT_USER_POOL_ID']\n\ndef create_tenant_admin_user(event, context):\n    app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    \n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']        \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user \")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n    \n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                   \n\n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n        user_info = get_user_info(event, user_pool_id, user_name)\n        logger.log_with_tenant_context(event, \"Request completed to get new user \")\n        return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")        \n        return utils.create_unauthorized_response()\n    else:\n        metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)            \n        response = client.admin_update_user_attributes(\n            Username=user_name,\n            UserPoolId=user_pool_id,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                }\n            ]\n        )\n        logger.log_with_tenant_context(event, response)\n\n        logger.log_with_tenant_context(event, \"Request completed to update user\")\n        \n        return utils.create_success_response(\"user updated\")    \n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n        response = client.admin_disable_user(\n            Username=user_name,\n            UserPoolId=user_pool_id\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n        return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n    \n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info    \n\nclass UserManagement:\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Lab3/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\n"
  },
  {
    "path": "Lab3/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Lab3/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n#TODO: Implement the below method\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    pass\n\n"
  },
  {
    "path": "Lab3/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab3/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth\n\ndef get_headers(event):\n    return event['headers']\n"
  },
  {
    "path": "Lab3/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn            \n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n  \nOutputs:  \n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Lab3/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  \n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  "
  },
  {
    "path": "Lab3/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\" \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nResources:  \n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:  \n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Lab3/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String  \n  TenantDetailsTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String  \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  \n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  \n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n          TENANT_USER_POOL: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT: !Ref CognitoUserPoolClientId          \n          \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"\n      \n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"   \n      \n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n      \n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n      \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n      \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n      \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']]               \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n      \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n  \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"          \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n   \n  \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  "
  },
  {
    "path": "Lab3/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST \n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE              \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL          \nOutputs:\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Lab3/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'          \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName\n    Condition: IsNotRunningInEventEngine"
  },
  {
    "path": "Lab3/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab3/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]    \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  "
  },
  {
    "path": "Lab3/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\ncached=\"true\"\nparallel=\"true\"\n"
  },
  {
    "path": "Lab3/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies-pooled\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Product-pooled\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Order-pooled\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-product-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-product-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt ProductTable.Arn\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      \n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-order-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-order-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt OrderTable.Arn\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  #Tenant Authorizer\n  TenantAuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: tenant-authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: tenant-authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n    \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: TenantAuthorizerExecutionRole\n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt TenantAuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL: !ImportValue Serverless-SaaS-CognitoTenantUserPoolId\n          TENANT_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoTenantAppClientId\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-tenant-api-pooled\n      RetentionInDays: 30\n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: 'pooled-serverless-saas-tenant-api'\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n      \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Lab4/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab4/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab4/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab4/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab4/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab4/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab4/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab4/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab4/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Lab4/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab4/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab4/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab4/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab4/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab4/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab4/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab4/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Lab4/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab4/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab4/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab4/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab4/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab4/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab4/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab4/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Lab4/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Lab4/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Lab4/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Lab4/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Lab4/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab4/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab4/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab4/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { environment } from 'src/environments/environment';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab4/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab4/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle *ngIf=\"tenantNameRequired\">Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\" *ngIf=\"tenantNameRequired\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid && tenantNameRequired\"\n              >\n                <div *ngIf=\"tenantNameRequired; else loginTextBlock\">Submit</div>\n                <ng-template #loginTextBlock>Login</ng-template>\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n  tenantNameRequired: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {\n    if (\n      environment.userPoolId &&\n      environment.appClientId &&\n      environment.apiGatewayUrl\n    ) {\n      // If a tenant's cognito configuration is provided in the\n      // \"environment\" object, then we take that instead of asking\n      // the visitor to provide the name of their tenant in order\n      // to do a look-up for that tenant's cognito configuration.\n      localStorage.setItem('tenantName', 'PooledTenants');\n      localStorage.setItem('userPoolId', environment.userPoolId);\n      localStorage.setItem('appClientId', environment.appClientId);\n      localStorage.setItem('apiGatewayUrl', environment.apiGatewayUrl);\n      this.tenantNameRequired = false;\n    }\n  }\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    if (!this.tenantNameRequired) {\n      this.router.navigate(['/dashboard']);\n      return true;\n    }\n\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.shardId }}:{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm!\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Description</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"description\"\n            placeholder=\"Enter product description\"\n          />\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button mat-raised-button color=\"primary\" (click)=\"(submit)\">\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ""
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup | undefined;\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private route: ActivatedRoute,\n    private router: Router,\n    private productSvc: ProductService,\n    private fb: FormBuilder\n  ) {}\n\n  ngOnInit(): void {\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.productForm = this.fb.group({\n      productId: [''],\n      name: [''],\n      price: [''],\n      description: [''],\n    });\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.name }}</td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab4/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab4/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab4/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  regApiGatewayUrl:\n    'https://3vby5hwma9.execute-api.us-west-2.amazonaws.com/prod/',\n  apiGatewayUrl: 'https://hj4p6t6ob5.execute-api.us-west-2.amazonaws.com/prod/',\n  userPoolId: 'us-west-2_HAYgKc4Ws',\n  appClientId: '7e7hpl565vdrvkf4ief77bgnm',\n};\n"
  },
  {
    "path": "Lab4/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://k1lecgl9ye.execute-api.us-west-2.amazonaws.com/prod/',\n  apiGatewayUrl: 'https://885cs6u6m8.execute-api.us-west-2.amazonaws.com/prod/',\n  userPoolId: 'us-west-2_bCwYPJqrb',\n  appClientId: '6lv6qhvmh6ivgd94qsftruc994',\n};\n"
  },
  {
    "path": "Lab4/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab4/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab4/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab4/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab4/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Lab4/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab4/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab4/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab4/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab4/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab4/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab4/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab4/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab4/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab4/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab4/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab4/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab4/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab4/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab4/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab4/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab4/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab4/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab4/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Lab4/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab4/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab4/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab4/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab4/client/dummy.txt",
    "content": ""
  },
  {
    "path": "Lab4/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c\"\n  echo \"Command to deploy bootstrap server code: deployment.sh -b\"\n  echo \"Command to deploy tenant server code: deployment.sh -t\"\n  echo \"Command to deploy bootstrap & tenant server code: deployment.sh -s\" \n  echo \"Command to deploy server & client code: deployment.sh -s -c\"\n  exit 1      \nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        -s) server=1 ;;\n        -b) bootstrap=1 ;;\n        -t) tenant=1 ;;\n        -c) client=1 ;;\n        *) echo \"Unknown parameter passed: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Validating server code using pylint\"\n  cd ../server\n  python3 -m pylint -E -d E0401,E1111 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n  cd ../scripts\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]]; then\n  echo \"Bootstrap server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t shared-template.yaml --use-container\n  \n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\n  else\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n    \n  cd ../scripts\nfi  \n\nif [[ $server -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Tenant server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t tenant-template.yaml --use-container\n  sam deploy --config-file tenant-samconfig.toml --region=$REGION\n  cd ../scripts\nfi\n\nif [[ $client -eq 1 ]]; then\n  # Admin UI and Landing UI are configured in Lab2 \n  # App UI is configured in Lab3\n  echo \"Admin UI and Landing UI are configured in Lab2. App UI is configured in Lab3.\n  So, no UI code is built in this Lab4\"\n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n    ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n    LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n    APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\n  fi\n  \n\n\n  echo \"Admin site URL: https://$ADMIN_SITE_URL\"\n  echo \"Landing site URL: https://$LANDING_APP_SITE_URL\"\n  echo \"App site URL: https://$APP_SITE_URL\"\n  \nfi  \n\n"
  },
  {
    "path": "Lab4/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Lab4/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Lab4/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Lab4/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab4/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nimport metrics_manager\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\n\ntable_name = os.environ['ORDER_TABLE_NAME']\n\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n \ndef get_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    table = __get_dynamodb_table(event, dynamodb)\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" \n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    #TODO: Implement this method\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Lab4/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab4/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Lab4/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab4/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport metrics_manager\nimport random\nimport threading\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\n\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):  \n    table = __get_dynamodb_table(event, dynamodb)  \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):    \n    table = __get_dynamodb_table(event, dynamodb)\n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']  \n    table = __get_dynamodb_table(event, dynamodb)  \n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):    \n    table = __get_dynamodb_table(event, dynamodb)\n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)    \n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" \n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    #TODO: Implement this method"
  },
  {
    "path": "Lab4/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab4/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml\n\n"
  },
  {
    "path": "Lab4/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Lab4/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport utils\nimport auth_manager\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\ntenant_userpool_id = os.environ['TENANT_USER_POOL']\ntenant_appclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        userpool_id = tenant_userpool_id\n        appclient_id = tenant_appclient_id \n    \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n\n    authResponse = policy.build()\n\n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.SHARED_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n \n    context = {\n        'accesskey': credentials['AccessKeyId'], \n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'userPoolId': userpool_id,\n        'tenantId': tenant_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab4/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\n\nuserpool_id = os.environ['TENANT_USER_POOL']\nappclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n        \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n   \n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    #TODO : Add code for Fine-Grained-Access-Control\n    \n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab4/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Lab4/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport requests\nimport metrics_manager\nimport auth_manager\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nregion = os.environ['AWS_REGION']\n\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\n#This method has been locked down to be only called from tenant registration service\ndef create_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n\n    try:          \n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],                    \n                    'isActive': True                    \n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        \n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier']\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()   \n    \ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Lab4/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\n\nlambda_client = boto3.client('lambda')\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n\n        tenant_details['tenantId'] = tenant_id\n\n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Lab4/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport metrics_manager\nimport auth_manager\nimport utils\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\nuser_pool_id = os.environ['TENANT_USER_POOL_ID']\n\ndef create_tenant_admin_user(event, context):\n    app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    \n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']        \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user\")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n    \n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            logger.log_with_tenant_context(event, \"Request completed to get new user\")\n            return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")      \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)\n            response = client.admin_update_user_attributes(\n                Username=user_name,\n                UserPoolId=user_pool_id,\n                UserAttributes=[\n                    {\n                        'Name': 'email',\n                        'Value': user_details['userEmail']\n                    },\n                    {\n                        'Name': 'custom:userRole',\n                        'Value': user_details['userRole'] \n                    }\n                ]\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to update user\")\n            return utils.create_success_response(\"user updated\")    \n\n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n            response = client.admin_disable_user(\n                Username=user_name,\n                UserPoolId=user_pool_id\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n            return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n    \n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info\n\nclass UserManagement:\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Lab4/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\ndef getPolicyForUser(user_role, service_identifier, tenant_id, region, aws_account_id):\n    \"\"\" This method is being used by Authorizer to get appropriate policy by user role\n    Args:\n        user_role (string): UserRoles enum\n        tenant_id (string): \n        region (string): \n        aws_account_id (string):  \n    Returns:\n        string: policy that tenant needs to assume\n    \"\"\"\n    iam_policy = \"\"\n    \n    if (isSystemAdmin(user_role)):\n        iam_policy = __getPolicyForSystemAdmin(region, aws_account_id)\n    elif (isTenantAdmin(user_role)):\n        iam_policy = __getPolicyForTenantAdmin(tenant_id, service_identifier, region, aws_account_id)\n    elif (isTenantUser(user_role)):\n        iam_policy = __getPolicyForTenantUser(tenant_id, region, aws_account_id)\n    \n    return iam_policy\n\ndef __getPolicyForSystemAdmin(region, aws_account_id):\n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                    \"dynamodb:UpdateItem\",\n                    \"dynamodb:GetItem\",\n                    \"dynamodb:PutItem\",\n                    \"dynamodb:DeleteItem\",\n                    \"dynamodb:Query\",          \n                    \"dynamodb:Scan\"\n                  ],\n                  \"Resource\": [\n                       \"arn:aws:dynamodb:{0}:{1}:table/*\".format(region, aws_account_id),\n                  ]\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n    \ndef __getPolicyForTenantAdmin(tenant_id, sevice_identifier, region, aws_account_id):\n    if (sevice_identifier == utils.Service_Identifier.SHARED_SERVICES.value):\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:Query\"                   \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantUserMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantDetails\".format(region, aws_account_id)\n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringEquals\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"               \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantStackMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-Settings\".format(region, aws_account_id)\n                    ]\n                }\n            ]\n        }\n    else:\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"    \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"       \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                }\n            ]\n        }\n    return json.dumps(policy)\n\ndef __getPolicyForTenantUser(tenant_id, region, aws_account_id):\n    \n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"      \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              },\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"               \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              }\n          ]\n        }\n    \n    return json.dumps(policy)"
  },
  {
    "path": "Lab4/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Lab4/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Lab4/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab4/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n\nclass Service_Identifier(Enum):\n    SHARED_SERVICES     = \"SharedServices\"\n    BUSINESS_SERVICES    = \"BusinessServices\"\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth\n\ndef get_headers(event):\n    return event['headers']\n"
  },
  {
    "path": "Lab4/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn            \n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n  \nOutputs:  \n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Lab4/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  \n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  "
  },
  {
    "path": "Lab4/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\" \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nResources:  \n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:  \n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Lab4/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String  \n  TenantDetailsTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String  \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  \n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn \n  AuthorizerAccessRole:\n    Type: AWS::IAM::Role\n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      RoleName: authorizer-access-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS:\n                - !GetAtt 'AuthorizerExecutionRole.Arn'\n            Action:\n              - sts:AssumeRole       \n      Policies:\n        - PolicyName: authorizer-access-role-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:BatchGetItem     \n                  - dynamodb:GetItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Query\n                  - dynamodb:Scan     \n                Resource:  \n                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*                   \n  \n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerAccessRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n          TENANT_USER_POOL: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT: !Ref CognitoUserPoolClientId          \n          \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"\n      \n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"   \n      \n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n      \n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n      \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n      \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n      \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']]               \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n      \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n  \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"          \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n   \n  \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn\n  AuthorizerExecutionRoleArn:\n    Value: !GetAtt AuthorizerExecutionRole.Arn        \n  "
  },
  {
    "path": "Lab4/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST \n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE              \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL          \nOutputs:\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Lab4/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'          \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName\n    Condition: IsNotRunningInEventEngine"
  },
  {
    "path": "Lab4/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab4/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]   \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  \n  AuthorizerExecutionRoleArn:\n    Description: The Lambda authorizer execution role\n    Value: !GetAtt LambdaFunctions.Outputs.AuthorizerExecutionRoleArn \n    Export:\n      Name: \"Serverless-SaaS-AuthorizerExecutionRoleArn\"   "
  },
  {
    "path": "Lab4/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\ncached=\"true\"\nparallel=\"true\"\n"
  },
  {
    "path": "Lab4/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies-pooled\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Product-pooled\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Order-pooled\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-product-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-product-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt ProductTable.Arn\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      \n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-order-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-order-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt OrderTable.Arn\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !ImportValue Serverless-SaaS-AuthorizerExecutionRoleArn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL: !ImportValue Serverless-SaaS-CognitoTenantUserPoolId\n          TENANT_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoTenantAppClientId\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-tenant-api-pooled\n      RetentionInDays: 30\n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: 'pooled-serverless-saas-tenant-api'\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n      \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Lab5/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab5/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab5/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab5/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab5/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab5/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab5/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab5/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab5/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Lab5/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab5/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab5/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab5/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab5/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab5/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab5/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab5/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Lab5/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab5/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab5/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab5/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab5/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab5/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab5/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab5/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Lab5/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Lab5/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Lab5/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Lab5/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Lab5/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab5/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab5/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab5/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab5/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab5/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle *ngIf=\"tenantNameRequired\">Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\" *ngIf=\"tenantNameRequired\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid && tenantNameRequired\"\n              >\n                <div *ngIf=\"tenantNameRequired; else loginTextBlock\">Submit</div>\n                <ng-template #loginTextBlock>Login</ng-template>\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n  tenantNameRequired: boolean = true;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {\n    if (\n      'userPoolId' in environment &&\n      'appClientId' in environment &&\n      'apiGatewayUrl' in environment\n    ) {\n      // If a tenant's cognito configuration is provided in the\n      // \"environment\" object, then we take that instead of asking\n      // the visitor to provide the name of their tenant in order\n      // to do a look-up for that tenant's cognito configuration.\n      localStorage.setItem('tenantName', 'PooledTenants');\n      localStorage.setItem('userPoolId', (environment as any).userPoolId);\n      localStorage.setItem('appClientId', (environment as any).appClientId);\n      localStorage.setItem('apiGatewayUrl', (environment as any).apiGatewayUrl);\n      this.tenantNameRequired = false;\n    }\n  }\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    if (!this.tenantNameRequired) {\n      this.router.navigate(['/dashboard']);\n      return true;\n    }\n\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.shardId }}:{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Edit Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Product ID</mat-label>\n          <input\n            matInput\n            value=\"{{ productId$ | async }}\"\n            [readonly]=\"true\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select formControlName=\"category\" required>\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{ category }}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router,\n    private route: ActivatedRoute\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      shardId: [],\n      productId: [],\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/products/edit/{{ element.shardId }}:{{ element.productId }}\">\n              {{ element.name }}\n            </a>\n          </td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab5/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab5/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab5/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Lab5/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Lab5/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab5/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab5/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab5/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab5/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Lab5/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab5/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab5/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab5/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab5/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab5/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab5/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab5/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab5/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab5/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab5/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab5/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab5/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab5/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab5/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab5/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab5/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab5/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab5/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Lab5/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab5/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab5/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab5/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab5/scripts/deploy-updates.sh",
    "content": "#!/bin/bash\ncd ../server || exit # stop execution if cd fails\nrm -rf .aws-sam/\npython3 -m pylint -E -d E0401,E0606 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n  \n#Deploying shared services changes\necho \"Deploying shared services changes\"  \necho Y | sam sync --stack-name serverless-saas -t shared-template.yaml --code --resource-id LambdaFunctions/CreateTenantAdminUserFunction --resource-id LambdaFunctions/ProvisionTenantFunction -u\n\ncd ../scripts || exit\n./geturl.sh"
  },
  {
    "path": "Lab5/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c\"\n  echo \"Command to deploy bootstrap server code: deployment.sh -b\"\n  echo \"Command to deploy CI/CD pipeline code: deployment.sh -p\"\n  echo \"Command to deploy CI/CD pipeline, bootstrap & tenant server code: deployment.sh -s\" \n  echo \"Command to deploy server & client code: deployment.sh -s -c\"\n  exit 1      \nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        -s) server=1 ;;\n        -b) bootstrap=1 ;;        \n        -p) pipeline=1 ;;\n        -c) client=1 ;;\n        *) echo \"Unknown parameter passed: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSiteBucket'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\n\n\nif [[ $server -eq 1 ]] || [[ $pipeline -eq 1 ]]; then\n  echo \"CI/CD pipeline code is getting deployed\"\n  #Create CodeCommit repo\n  REGION=$(aws configure get region)\n  REPO=$(aws codecommit get-repository --repository-name aws-serverless-saas-workshop)\n  if [[ $? -ne 0 ]]; then\n      echo \"aws-serverless-saas-workshop codecommit repo is not present, will create one now\"\n      CREATE_REPO=$(aws codecommit create-repository --repository-name aws-serverless-saas-workshop --repository-description \"Serverless SaaS workshop repository\")\n      echo $CREATE_REPO\n      REPO_URL=\"codecommit::${REGION}://aws-serverless-saas-workshop\"\n      git remote add cc $REPO_URL\n      if [[ $? -ne 0 ]]; then\n           echo \"Setting url to remote cc\"\n           git remote set-url cc $REPO_URL\n      fi\n      git push --set-upstream cc main\n  fi\n\n  #Deploying CI/CD pipeline\n  cd ../server/TenantPipeline/\n  npm install && npm run build \n  cdk bootstrap  \n  cdk deploy --require-approval never\n\n  cd ../../scripts\n\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]]; then\n  echo \"Bootstrap server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  echo \"Validating server code using pylint\"\n  python3 -m pylint -E -d E0401,E0606 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\" -not -path \"./TenantPipeline/node_modules/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n\n  sam build -t shared-template.yaml --use-container\n  \n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\n  else\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n    \n\n  cd ../scripts\n\nfi\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSiteBucket'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\n\n\nif [[ $client -eq 1 ]]; then\n  echo \"Client code is getting deployed\"\n  \n  ADMIN_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminApi'].OutputValue\" --output text)\n  \n  # Admin UI and Landing UI are configured in Lab2 \n  echo \"Admin UI and Landing UI are configured in Lab2. Only App UI will be reconfigured in this Lab5.\"\n  \n  # Configuring app UI \n\n  echo \"aws s3 ls s3://$APP_SITE_BUCKET\"\n  aws s3 ls s3://$APP_SITE_BUCKET \n  if [ $? -ne 0 ]; then\n      echo \"Error! S3 Bucket: $APP_SITE_BUCKET not readable\"\n      exit 1\n  fi\n\n  cd ../client/Application\n\n  echo \"Configuring environment for App Client\"\n\n  cat << EoF > ./src/environments/environment.prod.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n  };\nEoF\n  cat << EoF > ./src/environments/environment.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n  };\nEoF\n\n  npm install --legacy-peer-deps && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET\"\n  aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET \n\n  if [[ $? -ne 0 ]]; then\n      exit 1\n  fi\n\n  echo \"Completed configuring environment for App Client\"\n  echo \"Successfully completed redeploying Application UI\"\n\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\"\n  \n"
  },
  {
    "path": "Lab5/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Lab5/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Lab5/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Lab5/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab5/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nimport metrics_manager\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n \n\ndef get_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n\n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    table = __get_dynamodb_table(event, dynamodb)\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" Determine the table name based upo pooled vs silo model\n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )        \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Lab5/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab5/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Lab5/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab5/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport metrics_manager\nimport random\nimport threading\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']    \n    table = __get_dynamodb_table(event, dynamodb)\n\n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):    \n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):    \n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )       \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n"
  },
  {
    "path": "Lab5/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab5/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml\n\n"
  },
  {
    "path": "Lab5/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Lab5/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\n\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']        \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n        \n\n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.SHARED_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab5/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user     \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']\n        apigateway_url = tenant_details['Item']['apiGatewayUrl']\n        \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    if (auth_manager.isSaaSProvider(user_role) == False):\n        if (isTenantAuthorizedForThisAPI(apigateway_url, api_gateway_arn_tmp[0]) == False):\n            logger.error('Unauthorized')\n            raise Exception('Unauthorized')\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.BUSINESS_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef isTenantAuthorizedForThisAPI(apigateway_url, current_api_id):\n    if(apigateway_url.split('.')[0] != 'https://' + current_api_id):\n        return False\n    else:\n        return True\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab5/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Lab5/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport metrics_manager\nimport auth_manager\nimport requests\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n\nregion = os.environ['AWS_REGION']\n\n#This method has been locked down to be only\ndef create_tenant(event, context):\n    api_gateway_url = ''       \n    tenant_details = json.loads(event['body'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    table_system_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n    try:          \n        # for pooled tenants the apigateway url is saving in settings during stack creation\n        # update from there during tenant creation\n        if(tenant_details['dedicatedTenancy'].lower()!= 'true'):\n            settings_response = table_system_settings.get_item(\n                Key={\n                    'settingName': 'apiGatewayUrl-Pooled'\n                } \n            )\n            api_gateway_url = settings_response['Item']['settingValue']\n\n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],\n                    'userPoolId': tenant_details['userPoolId'],                 \n                    'appClientId': tenant_details['appClientId'],\n                    'dedicatedTenancy': tenant_details['dedicatedTenancy'],\n                    'isActive': True,\n                    'apiGatewayUrl': api_gateway_url\n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n\n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n\n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier']\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    url_deprovision_tenant = os.environ['DEPROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            update_user_response = __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, url_deprovision_tenant)\n\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()    \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    url_provision_tenant = os.environ['PROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            provision_response = __invoke_provision_tenant(update_details, headers, auth, host, stage_name, url_provision_tenant)\n            logger.log_with_tenant_context(event, provision_response)\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()    \n\ndef load_tenant_config(event, context):\n    params = event['pathParameters']\n    tenantName = urllib.parse.unquote(params['tenantname'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    try:\n        response = table_tenant_details.query(\n            IndexName=\"ServerlessSaas-TenantConfig\",\n            KeyConditionExpression=Key('tenantName').eq(tenantName),\n            ProjectionExpression=\"userPoolId, appClientId, apiGatewayUrl\"\n        ) \n    except Exception as e:\n        raise Exception('Error getting tenant config', e)\n    else:\n        if (response['Count'] == 0):\n            return utils.create_notfound_response(\"Tenant not found.\"+\n            \"Please enter exact tenant name used during tenant registration.\")\n        else:\n            return utils.generate_response(response['Items'][0])        \n\ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url + update_details['tenantId']])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while deprovisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while deprovisioning tenant')\n        raise Exception('Error occured while deprovisioning tenant', e) \n    else:\n        return \"Success invoking deprovision tenant\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\ndef __invoke_provision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.post(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while provisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while provisioning tenant')\n        raise Exception('Error occured while provisioning tenant', e) \n    else:\n        return \"Success invoking provision tenant\"\n\ndef __getTenantManagementTable(event):\n    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n    dynamodb = boto3.resource('dynamodb', aws_access_key_id=accesskey, aws_secret_access_key=secretkey, aws_session_token=sessiontoken)\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    return table_tenant_details\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Lab5/server/TenantManagementService/tenant-provisioning.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport os\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\ntenant_stack_mapping_table_name = os.environ['TENANT_STACK_MAPPING_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ncodepipeline = boto3.client('codepipeline')\ncloudformation = boto3.client('cloudformation')\ntable_tenant_stack_mapping = dynamodb.Table(tenant_stack_mapping_table_name)\n\nstack_name = 'stack-{0}'\n@tracer.capture_lambda_handler\ndef provision_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n    \n    try:          \n        \n        #TODO: Add missing code to kick off the pipeline\n        pass\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Provisioning Started\")\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef deprovision_tenant(event, context):\n    logger.info(\"Request received to deprovision a tenant\")\n    \n    tenantid_to_deprovision = event['tenantId']\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.delete_item(\n            Key={\n                    'tenantId': tenantid_to_deprovision                    \n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_cloudformation = cloudformation.delete_stack(\n            StackName=stack_name.format(tenantid_to_deprovision)\n        )\n\n        logger.info(response_cloudformation)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Deprovisioning Started\")\n\n \n"
  },
  {
    "path": "Lab5/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\nprovision_tenant_resource_path = os.environ['PROVISION_TENANT_RESOURCE_PATH']\n\n\nlambda_client = boto3.client('lambda')\n\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n        tenant_details['dedicatedTenancy'] = 'false'\n\n        if (tenant_details['tenantTier'].upper() == utils.TenantTier.PLATINUM.value.upper()):\n            tenant_details['dedicatedTenancy'] = 'true'\n        \n        tenant_details['tenantId'] = tenant_id\n        \n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['userPoolId'] = create_user_response['message']['userPoolId']\n        tenant_details['appClientId'] = create_user_response['message']['appClientId']\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n        if (tenant_details['dedicatedTenancy'].upper() == 'TRUE'):\n            provision_tenant_response = __provision_tenant(tenant_details, headers, auth, host, stage_name)\n            logger.info(provision_tenant_response)\n\n        \n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\ndef __provision_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, provision_tenant_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()['message']\n    except Exception as e:\n        logger.error('Error occured while provisioning the tenant')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Lab5/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport utils\nimport metrics_manager\nimport auth_manager\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\ndef create_tenant_admin_user(event, context):\n    tenant_user_pool_id = os.environ['TENANT_USER_POOL_ID']\n    tenant_app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    \n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    if (tenant_details['dedicatedTenancy'] == 'true'):\n        #TODO: add code to provision new user pool\n        pass\n    else:\n        user_pool_id = tenant_user_pool_id\n        app_client_id = tenant_app_client_id\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    \n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']    \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user\")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                    \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n\n\n\n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']      \n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            logger.log_with_tenant_context(event, \"Request completed to get new user\")\n            return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")         \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)            \n            response = client.admin_update_user_attributes(\n                Username=user_name,\n                UserPoolId=user_pool_id,\n                UserAttributes=[\n                    {\n                        'Name': 'email',\n                        'Value': user_details['userEmail']\n                    },\n                    {\n                        'Name': 'custom:userRole',\n                        'Value': user_details['userRole'] \n                    }\n                ]\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to update user\")\n            return utils.create_success_response(\"user updated\")    \n\n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n            response = client.admin_disable_user(\n                    Username=user_name,\n                    UserPoolId=user_pool_id\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n            return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info\n\nclass UserManagement:\n    def create_user_pool(self, tenant_id):\n        application_site_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        email_message = ''.join([\"Login into tenant UI application at \", \n                        application_site_url,\n                        \" with username {username} and temporary password {####}\"])\n        email_subject = \"Your temporary password for tenant UI application\"  \n        response = client.create_user_pool(\n            PoolName= tenant_id + '-ServerlessSaaSUserPool',\n            AutoVerifiedAttributes=['email'],\n            AccountRecoverySetting={\n                'RecoveryMechanisms': [\n                    {\n                        'Priority': 1,\n                        'Name': 'verified_email'\n                    },\n                ]\n            },\n            Schema=[\n                {\n                    'Name': 'email',\n                    'AttributeDataType': 'String',\n                    'Required': True,                    \n                },\n                {\n                    'Name': 'tenantId',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                },            \n                {\n                    'Name': 'userRole',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                }\n            ],\n            AdminCreateUserConfig={\n                'InviteMessageTemplate': {\n                    'EmailMessage': email_message,\n                    'EmailSubject': email_subject\n                }\n            }\n        )    \n        return response\n\n    def create_user_pool_client(self, user_pool_id):\n        user_pool_callback_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        response = client.create_user_pool_client(\n            UserPoolId= user_pool_id,\n            ClientName= 'ServerlessSaaSClient',\n            GenerateSecret= False,\n            AllowedOAuthFlowsUserPoolClient= True,\n            AllowedOAuthFlows=[\n                'code', 'implicit'\n            ],\n            SupportedIdentityProviders=[\n                'COGNITO',\n            ],\n            CallbackURLs=[\n                user_pool_callback_url,\n            ],\n            LogoutURLs= [\n                user_pool_callback_url,\n            ],\n            AllowedOAuthScopes=[\n                'email',\n                'openid',\n                'profile'\n            ],\n            WriteAttributes=[\n                'email',\n                'custom:tenantId'\n            ]\n        )\n        return response\n\n    def create_user_pool_domain(self, user_pool_id, tenant_id):\n        response = client.create_user_pool_domain(\n            Domain= tenant_id + '-serverlesssaas',\n            UserPoolId=user_pool_id\n        )\n        return response\n\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Lab5/server/TenantPipeline/.gitignore",
    "content": "*.js\n!jest.config.js\n*.d.ts\nnode_modules\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n\n# Parcel default cache directory\n.parcel-cache\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/.npmignore",
    "content": "*.ts\n!*.d.ts\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/README.md",
    "content": "# Welcome to your CDK TypeScript project!\n\nThis is a blank project for TypeScript development with CDK.\n\nThe `cdk.json` file tells the CDK Toolkit how to execute your app.\n\n## Useful commands\n\n * `npm run build`   compile typescript to js\n * `npm run watch`   watch for changes and compile\n * `npm run test`    perform the jest unit tests\n * `cdk deploy`      deploy this stack to your default AWS account/region\n * `cdk diff`        compare deployed stack with current state\n * `cdk synth`       emits the synthesized CloudFormation template\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/bin/pipeline.ts",
    "content": "#!/usr/bin/env node\nimport 'source-map-support/register';\nimport * as cdk from 'aws-cdk-lib';\nimport { ServerlessSaaSStack } from '../lib/serverless-saas-stack';\n\nconst app = new cdk.App();\nnew ServerlessSaaSStack(app, 'serverless-saas-pipeline');\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/cdk.json",
    "content": "{\n  \"app\": \"npx ts-node bin/pipeline.ts\",\n  \"context\": {}\n}\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/jest.config.js",
    "content": "module.exports = {\n  roots: ['<rootDir>/test'],\n  testMatch: ['**/*.test.ts'],\n  transform: {\n    '^.+\\\\.tsx?$': 'ts-jest'\n  }\n};\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/lib/serverless-saas-stack.ts",
    "content": "// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: MIT-0\n\nimport { Construct } from 'constructs';\nimport * as cdk from 'aws-cdk-lib';\n\nimport * as s3 from 'aws-cdk-lib/aws-s3';\nimport * as codecommit from 'aws-cdk-lib/aws-codecommit';\nimport * as codepipeline from 'aws-cdk-lib/aws-codepipeline';\nimport * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';\nimport * as codebuild from 'aws-cdk-lib/aws-codebuild';\n\nimport { Function, Runtime, AssetCode } from 'aws-cdk-lib/aws-lambda';\nimport { PolicyStatement } from 'aws-cdk-lib/aws-iam';\nimport { Duration } from 'aws-cdk-lib';\n\n\nexport class ServerlessSaaSStack extends cdk.Stack {\n  constructor(scope: Construct, id: string, props?: cdk.StackProps) {\n    super(scope, id, props);\n\n    const artifactsBucket = new s3.Bucket(this, \"ArtifactsBucket\", {\n      encryption: s3.BucketEncryption.S3_MANAGED,\n    });\n\n    //Since this lambda is invoking cloudformation which is inturn deploying AWS resources, we are giving overly permissive permissions to this lambda. \n    //You can limit this based upon your use case and AWS Resources you need to deploy.\n    const lambdaPolicy = new PolicyStatement()\n        lambdaPolicy.addActions(\"*\")\n        lambdaPolicy.addResources(\"*\")\n\n    const lambdaFunction = new Function(this, \"deploy-tenant-stack\", {\n        handler: \"lambda-deploy-tenant-stack.lambda_handler\",\n        runtime: Runtime.PYTHON_3_9,\n        code: new AssetCode(`./resources`),\n        memorySize: 512,\n        timeout: Duration.seconds(10),\n        environment: {\n            BUCKET: artifactsBucket.bucketName,\n        },\n        initialPolicy: [lambdaPolicy],\n    })\n\n    // Pipeline creation starts\n    const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {\n      pipelineName: 'serverless-saas-pipeline',\n      artifactBucket: artifactsBucket\n    });\n\n    // Import existing CodeCommit sam-app repository\n    const codeRepo = codecommit.Repository.fromRepositoryName(\n      this,\n      'AppRepository', \n      'aws-serverless-saas-workshop' \n    );\n\n    // Declare source code as an artifact\n    const sourceOutput = new codepipeline.Artifact();\n\n    // Add source stage to pipeline\n    pipeline.addStage({\n      stageName: 'Source',\n      actions: [\n        new codepipeline_actions.CodeCommitSourceAction({\n          actionName: 'CodeCommit_Source',\n          repository: codeRepo,\n          branch: 'main',\n          output: sourceOutput,\n          variablesNamespace: 'SourceVariables'\n        }),\n      ],\n    });\n\n    // Declare build output as artifacts\n    const buildOutput = new codepipeline.Artifact();\n\n\n\n    //Declare a new CodeBuild project\n    const buildProject = new codebuild.PipelineProject(this, 'Build', {\n      buildSpec : codebuild.BuildSpec.fromSourceFilename(\"Lab5/server/tenant-buildspec.yml\"),\n      environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4 },\n      environmentVariables: {\n        'PACKAGE_BUCKET': {\n          value: artifactsBucket.bucketName\n        }\n      }\n    });\n\n    \n\n    // Add the build stage to our pipeline\n    pipeline.addStage({\n      stageName: 'Build',\n      actions: [\n        new codepipeline_actions.CodeBuildAction({\n          actionName: 'Build-Serverless-SaaS',\n          project: buildProject,\n          input: sourceOutput,\n          outputs: [buildOutput],\n        }),\n      ],\n    });\n\n    const deployOutput = new codepipeline.Artifact();\n\n\n    //Add the Lambda function that will deploy the tenant stack in a multitenant way\n    pipeline.addStage({\n      stageName: 'Deploy',\n      actions: [\n        new codepipeline_actions.LambdaInvokeAction({\n          actionName: 'DeployTenantStack',\n          lambda: lambdaFunction,\n          inputs: [buildOutput],\n          outputs: [deployOutput],\n          userParameters: {\n            'artifact': 'Artifact_Build_Build-Serverless-SaaS',\n            'template_file': 'packaged.yaml',\n            'commit_id': '#{SourceVariables.CommitId}'\n          }\n        }),\n      ],\n    });    \n  }\n}\n"
  },
  {
    "path": "Lab5/server/TenantPipeline/package.json",
    "content": "\n{\n  \"name\": \"pipeline\",\n  \"version\": \"0.1.0\",\n  \"bin\": {\n    \"pipeline\": \"bin/pipeline.js\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc -w\",\n    \"test\": \"jest\",\n    \"cdk\": \"cdk\"\n  },\n  \"devDependencies\": {\n    \"@aws-cdk/assert\": \"1.64.1\",\n    \"@types/jest\": \"^26.0.10\",\n    \"@types/node\": \"10.17.27\",\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"node-notifier\": \"^8.0.1\",\n    \"ts-jest\": \"^26.2.0\",\n    \"ts-node\": \"^8.1.0\",\n    \"typescript\": \"4.9.5\",\n    \"@types/prettier\": \"2.6.0\"\n  },\n  \"dependencies\": {\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"source-map-support\": \"^0.5.19\"\n  }\n}"
  },
  {
    "path": "Lab5/server/TenantPipeline/resources/lambda-deploy-tenant-stack.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom boto3.session import Session\n\nimport json\nimport boto3\nimport zipfile\nimport tempfile\nimport botocore\nimport traceback\nimport time\n\n\n\nprint('Loading function')\n\ncf = boto3.client('cloudformation')\ncode_pipeline = boto3.client('codepipeline')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_stack_mapping = dynamodb.Table('ServerlessSaaS-TenantStackMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\ntable_tenant_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n\ndef find_artifact(artifacts, name):\n    \"\"\"Finds the artifact 'name' among the 'artifacts'\n    \n    Args:\n        artifacts: The list of artifacts available to the function\n        name: The artifact we wish to use\n    Returns:\n        The artifact dictionary found\n    Raises:\n        Exception: If no matching artifact is found\n    \n    \"\"\"\n    for artifact in artifacts:\n        if artifact['name'] == name:\n            return artifact\n            \n    raise Exception('Input artifact named \"{0}\" not found in event'.format(name))\n\ndef get_template_url(s3, artifact, file_in_zip):\n    \"\"\"Gets the template artifact\n    \n    Downloads the artifact from the S3 artifact store to a temporary file\n    then extracts the zip and returns the file containing the CloudFormation\n    template.\n    \n    Args:\n        artifact: The artifact to download\n        file_in_zip: The path to the file within the zip containing the template\n        \n    Returns:\n        The CloudFormation template as a string\n        \n    Raises:\n        Exception: Any exception thrown while downloading the artifact or unzipping it\n    \n    \"\"\"\n    tmp_file = tempfile.NamedTemporaryFile()\n    bucket = artifact['location']['s3Location']['bucketName']\n    print(bucket)\n\n    key = artifact['location']['s3Location']['objectKey']\n    print(key)    \n    with tempfile.NamedTemporaryFile() as tmp_file:\n        s3.download_file(bucket, key, tmp_file.name)\n        with zipfile.ZipFile(tmp_file.name, 'r') as zip:\n            extracted_file = zip.extract(file_in_zip, '/tmp/')\n            s3.upload_file(extracted_file, bucket, file_in_zip)\n            template_url =''.join(['https://', bucket,'.s3.amazonaws.com/',file_in_zip])\n            return template_url  \n\n            \n   \ndef update_stack(stack, template_url, params):\n    \"\"\"Start a CloudFormation stack update\n    \n    Args:\n        stack: The stack to update\n        template_url: The template to apply\n        \n    Returns:\n        True if an update was started, false if there were no changes\n        to the template since the last update.\n        \n    Raises:\n        Exception: Any exception besides \"No updates are to be performed.\"\n    \n    \"\"\"\n    try:\n        cf.update_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n        return True\n        \n    except botocore.exceptions.ClientError as e:\n        if e.response['Error']['Message'] == 'No updates are to be performed.':\n            return False\n        else:\n            raise Exception('Error updating CloudFormation stack \"{0}\"'.format(stack), e)\n\ndef stack_exists(stack):\n    \"\"\"Check if a stack exists or not\n    \n    Args:\n        stack: The stack to check\n        \n    Returns:\n        True or False depending on whether the stack exists\n        \n    Raises:\n        Any exceptions raised .describe_stacks() besides that\n        the stack doesn't exist.\n        \n    \"\"\"\n    try:\n        cf.describe_stacks(StackName=stack)\n        return True\n    except botocore.exceptions.ClientError as e:\n        if \"does not exist\" in e.response['Error']['Message']:\n            return False\n        else:\n            raise e\n\ndef create_stack(stack, template_url, params):\n    \"\"\"Starts a new CloudFormation stack creation\n    \n    Args:\n        stack: The stack to be created\n        template_url: The template for the stack to be created with\n        \n    Throws:\n        Exception: Any exception thrown by .create_stack()\n    \"\"\"\n    cf.create_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n \ndef get_stack_status(stack):\n    \"\"\"Get the status of an existing CloudFormation stack\n    \n    Args:\n        stack: The name of the stack to check\n        \n    Returns:\n        The CloudFormation status string of the stack such as CREATE_COMPLETE\n        \n    Raises:\n        Exception: Any exception thrown by .describe_stacks()\n        \n    \"\"\"\n    stack_description = cf.describe_stacks(StackName=stack)\n    return stack_description['Stacks'][0]['StackStatus']\n  \ndef put_job_success(job, message):\n    \"\"\"Notify CodePipeline of a successful job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    print('Putting job success')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job)\n  \ndef put_job_failure(job, message):\n    \"\"\"Notify CodePipeline of a failed job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_failure_result()\n    \n    \"\"\"\n    print('Putting job failure')\n    print(message)\n    code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'})\n \ndef continue_job_later(job, message):\n    \"\"\"Notify CodePipeline of a continuing job\n    \n    This will cause CodePipeline to invoke the function again with the\n    supplied continuation token.\n    \n    Args:\n        job: The JobID\n        message: A message to be logged relating to the job status\n        continuation_token: The continuation token\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    \n    # Use the continuation token to keep track of any job execution state\n    # This data will be available when a new job is scheduled to continue the current execution\n    continuation_token = json.dumps({'previous_job_id': job})\n    \n    print('Putting job continuation')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job, continuationToken=continuation_token)\n\ndef start_update_or_create(job_id, stack, template_url, params):\n    \"\"\"Starts the stack update or create process\n    \n    If the stack exists then update, otherwise create.\n    \n    Args:\n        job_id: The ID of the CodePipeline job\n        stack: The stack to create or update\n        template_url: The template to create/update the stack with\n    \n    \"\"\"\n    if stack_exists(stack):\n        status = get_stack_status(stack)\n        if status not in ['CREATE_COMPLETE', 'ROLLBACK_COMPLETE', 'UPDATE_COMPLETE']:\n            # If the CloudFormation stack is not in a state where\n            # it can be updated again then fail the job right away.\n            put_job_failure(job_id, 'Stack cannot be updated when status is: ' + status)\n            return\n        \n        were_updates = update_stack(stack, template_url, params)\n        \n        if were_updates:\n            # If there were updates then continue the job so it can monitor\n            # the progress of the update.\n            continue_job_later(job_id, 'Stack update started')  \n            \n        else:\n            # If there were no updates then succeed the job immediately \n            put_job_success(job_id, 'There were no stack updates')    \n    else:\n        # If the stack doesn't already exist then create it instead\n        # of updating it.\n        create_stack(stack, template_url, params)\n        # Continue the job so the pipeline will wait for the CloudFormation\n        # stack to be created.\n        continue_job_later(job_id, 'Stack create started') \n\ndef check_stack_update_status(job_id, stack):\n    \"\"\"Monitor an already-running CloudFormation update/create\n    \n    Succeeds, fails or continues the job depending on the stack status.\n    \n    Args:\n        job_id: The CodePipeline job ID\n        stack: The stack to monitor\n    \n    \"\"\"\n    status = get_stack_status(stack)\n    if status in ['UPDATE_COMPLETE', 'CREATE_COMPLETE']:\n        # If the update/create finished successfully then\n        # succeed the job and don't continue.\n        put_job_success(job_id, 'Stack update complete')\n        \n    elif status in ['UPDATE_IN_PROGRESS', 'UPDATE_ROLLBACK_IN_PROGRESS', \n    'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', 'CREATE_IN_PROGRESS', \n    'ROLLBACK_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS']:\n        # If the job isn't finished yet then continue it\n        continue_job_later(job_id, 'Stack update still in progress') \n       \n    else:\n        # If the Stack is a state which isn't \"in progress\" or \"complete\"\n        # then the stack update/create has failed so end the job with\n        # a failed result.\n        put_job_failure(job_id, 'Update failed: ' + status)\n\ndef get_user_params(job_data):\n    \"\"\"Decodes the JSON user parameters and validates the required properties.\n    \n    Args:\n        job_data: The job data structure containing the UserParameters string which should be a valid JSON structure\n        \n    Returns:\n        The JSON parameters decoded as a dictionary.\n        \n    Raises:\n        Exception: The JSON can't be decoded or a property is missing.\n        \n    \"\"\"\n    try:\n        # Get the user parameters which contain the stack, artifact and file settings\n        user_parameters = job_data['actionConfiguration']['configuration']['UserParameters']\n        decoded_parameters = json.loads(user_parameters)\n            \n    except Exception:\n        # We're expecting the user parameters to be encoded as JSON\n        # so we can pass multiple values. If the JSON can't be decoded\n        # then fail the job with a helpful message.\n        raise Exception('UserParameters could not be decoded as JSON')\n    \n\n    if 'artifact' not in decoded_parameters:\n        # Validate that the artifact name is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the artifact name')\n    \n    if 'template_file' not in decoded_parameters:\n        # Validate that the template file is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the template file name')\n    \n    return decoded_parameters\n    \ndef setup_s3_client(job_data):\n    \"\"\"Creates an S3 client\n    \n    Uses the credentials passed in the event by CodePipeline. These\n    credentials can be used to access the artifact bucket.\n    \n    Args:\n        job_data: The job data structure\n        \n    Returns:\n        An S3 client with the appropriate credentials\n        \n    \"\"\"\n    # Could not use the artifact credentials to put object to artifacts s3 bucket.\n    # We are running into issue as described in https://github.com/aws/aws-cdk/issues/3274\n     \n    # key_id = job_data['artifactCredentials']['accessKeyId']\n    # key_secret = job_data['artifactCredentials']['secretAccessKey']\n    # session_token = job_data['artifactCredentials']['sessionToken']\n    \n    # session = Session(aws_access_key_id=key_id,\n    #     aws_secret_access_key=key_secret,\n    #     aws_session_token=session_token)\n    # return session.client('s3')\n    return boto3.client('s3')\n\ndef get_tenant_params(tenantId):\n    \"\"\"Get tenant details to be supplied to Cloud formation\n\n    Args:\n        tenantId (str): tenantId for which details are needed\n\n    Returns:\n        params from tenant management table\n    \"\"\"\n    params = []\n    param_tenantid = {}\n    param_tenantid['ParameterKey'] = 'TenantIdParameter'\n    param_tenantid['ParameterValue'] = tenantId\n    params.append(param_tenantid)\n\n    return params\n\ndef add_parameter(params, parameter_key, parameter_value):\n    parameter = {}\n    parameter['ParameterKey'] = parameter_key\n    parameter['ParameterValue'] = parameter_value\n    params.append(parameter)\n\n\n\n\ndef update_tenantstackmapping(tenantId, commit_id):\n    \"\"\"Update the tenant stack mapping table with the code pipeline job id\n\n    Args:\n        tenantId ([string]): tenant id for which data needs to be updated\n        job_id ([type]): current code pipeline job id\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    response = table_tenant_stack_mapping.update_item(\n            Key={'tenantId': tenantId},\n            UpdateExpression=\"set codeCommitId=:codeCommitId\",\n            ExpressionAttributeValues={\n            ':codeCommitId': commit_id\n            },\n            ReturnValues=\"NONE\") \n    \n    return response\n\ndef lambda_handler(event, context):\n    \"\"\"The Lambda function handler\n    \n    If a continuing job then checks the CloudFormation stack status\n    and updates the job accordingly.\n    \n    If a new job then kick of an update or creation of the target\n    CloudFormation stack.\n    \n    Args:\n        event: The event passed by Lambda\n        context: The context passed by Lambda\n        \n    \"\"\"\n    try:\n        # Extract the Job ID\n        job_id = event['CodePipeline.job']['id']\n        \n        # Extract the Job Data \n        job_data = event['CodePipeline.job']['data']\n        \n        # Extract the params\n        params = get_user_params(job_data)\n        \n        # Get the list of artifacts passed to the function\n        artifacts = job_data['inputArtifacts']\n        \n        artifact = params['artifact']\n        template_file = params['template_file']\n        commit_id = params['commit_id']\n\n        # Get all the stacks for each tenant to be updated/created from tenant stack mapping table\n        mappings = table_tenant_stack_mapping.scan()\n        print (mappings)\n        #Update/Create stacks for all tenants\n        for mapping in mappings['Items']:\n            stack = mapping['stackName']\n            tenantId = mapping['tenantId']\n            applyLatestRelease = mapping['applyLatestRelease']\n\n            if (applyLatestRelease):\n                # Get the parameters to be passed to the Cloudformation from tenant table\n                params = get_tenant_params(tenantId)\n                \n                if 'continuationToken' in job_data:\n                    # If we're continuing then the create/update has already been triggered\n                    # we just need to check if it has finished.\n                    check_stack_update_status(job_id, stack)\n                else:\n                    # Get the artifact details\n                    artifact_data = find_artifact(artifacts, artifact)\n                    # Get S3 client to access artifact with\n                    s3 = setup_s3_client(job_data)\n                    # Get the JSON template file out of the artifact\n                    template_url = get_template_url(s3, artifact_data, template_file)\n                    \n                    # Kick off a stack update or create\n                    start_update_or_create(job_id, stack, template_url, params)  \n\n                    # If we are applying the release, update tenant stack mapping with the pipe line id\n                    update_tenantstackmapping(tenantId, commit_id)\n    except Exception as e:\n        # If any other exceptions which we didn't expect are raised\n        # then fail the job and log the exception message.\n        print('Function failed due to exception.') \n        print(e)\n        traceback.print_exc()\n        put_job_failure(job_id, 'Function exception: ' + str(e))\n    \n    #put_job_success(job_id, \"Changeset executed successfully\")\n    print('Function complete.')   \n    return \"Complete.\""
  },
  {
    "path": "Lab5/server/TenantPipeline/test/pipeline.test.ts",
    "content": "// import { SynthUtils } from '@aws-cdk/assert';\n// import { Stack, App } from 'aws-cdk-lib';\n// import { Template } from 'aws-cdk-lib/assertions';\n// import * as Pipeline from '../lib/serverless-saas-stack';\n\n// test('synthesized cloudformation template should match original template', () => {\n//     const app = new App();\n//     const stack = new Pipeline.ServerlessSaaSStack(app, 'MyTestStack');\n//     const template = Template.fromStack(stack);\n//     expect(template).toMatchSnapshot();\n// });"
  },
  {
    "path": "Lab5/server/TenantPipeline/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"es2018\"],\n    \"declaration\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": false,\n    \"inlineSourceMap\": true,\n    \"inlineSources\": true,\n    \"experimentalDecorators\": true,\n    \"strictPropertyInitialization\": false,\n    \"typeRoots\": [\"./node_modules/@types\"]\n  },\n  \"exclude\": [\"cdk.out\"]\n}\n"
  },
  {
    "path": "Lab5/server/custom_resources/requirements.txt",
    "content": "requests\ncrhelper"
  },
  {
    "path": "Lab5/server/custom_resources/update_settings_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" Called as part of bootstrap template. \n        Inserts/Updates Settings table based upon the resources deployed inside bootstrap template\n        We use these settings inside tenant template\n\n    Args:\n            event ([type]): [description]\n            _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating settings\")\n\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    cognitoUserPoolId = event['ResourceProperties']['cognitoUserPoolId']\n    cognitoUserPoolClientId = event['ResourceProperties']['cognitoUserPoolClientId']\n\n    table_system_settings = dynamodb.Table(settings_table_name)\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'userPoolId-pooled',\n                    'settingValue' : cognitoUserPoolId\n                }\n            )\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'appClientId-pooled',\n                    'settingValue' : cognitoUserPoolClientId\n                }\n            )\n\n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab5/server/custom_resources/update_tenant_apigatewayurl.py",
    "content": "import json\nimport boto3\nimport logger\nfrom boto3.dynamodb.conditions import Key\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    client = boto3.client('dynamodb')\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" The URL for Tenant APIs(Product/Order) can differ by tenant.\n        For Pooled tenants it is shared and for Silo (Platinum tier tenants) it is unique to them.\n        This method keeps the URL for Pooled tenants inside Settings Table, since it is shared across multiple tenants,\n        And for Silo tenants inside the tenant management table along with other tenant settings, for that tenant\n\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Details table\")\n\n    tenant_details_table_name = event['ResourceProperties']['TenantDetailsTableName']\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    tenant_id = event['ResourceProperties']['TenantId']\n    tenant_api_gateway_url = event['ResourceProperties']['TenantApiGatewayUrl']\n\n\n    if(tenant_id.lower() =='pooled'):\n        # Note: Tenant management service will use below setting to update apiGatewayUrl for pooled tenants in TenantDetails table\n        settings_table = dynamodb.Table(settings_table_name)\n        settings_table.put_item(Item={\n                    'settingName': 'apiGatewayUrl-Pooled',\n                    'settingValue' : tenant_api_gateway_url                    \n                })\n        \n    else:\n        tenant_details = dynamodb.Table(tenant_details_table_name)\n        response = tenant_details.update_item(\n            Key={'tenantId': tenant_id},\n            UpdateExpression=\"set apiGatewayUrl=:apiGatewayUrl\",\n            ExpressionAttributeValues={\n            ':apiGatewayUrl': tenant_api_gateway_url\n            },\n            ReturnValues=\"NONE\") \n                   \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab5/server/custom_resources/update_tenantstackmap_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" One time entry for pooled tenants inside tenant stack mapping table.\n        This ensures that when code pipeline for tenant template is kicked off, it always create a default stack for pooled tenants.\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Stack Map\")\n\n    tenantstackmap_table_name = event['ResourceProperties']['TenantStackMappingTableName']\n    \n    table_stack_mapping = dynamodb.Table(tenantstackmap_table_name)\n    \n    response = table_stack_mapping.put_item(\n            Item={\n                    'tenantId': 'pooled',\n                    'stackName' : 'stack-pooled',\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )                  \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab5/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\ndef getPolicyForUser(user_role, service_identifier, tenant_id, region, aws_account_id):\n    \"\"\" This method is being used by Authorizer to get appropriate policy by user role\n\n    Args:\n        user_role (string): UserRoles enum\n        tenant_id (string): \n        region (string): \n        aws_account_id (string):  \n\n    Returns:\n        string: policy that tenant needs to assume\n    \"\"\"\n    iam_policy = \"\"\n    \n    if (isSystemAdmin(user_role)):\n        iam_policy = __getPolicyForSystemAdmin(region, aws_account_id)\n    elif (isTenantAdmin(user_role)):\n        iam_policy = __getPolicyForTenantAdmin(tenant_id, service_identifier, region, aws_account_id)\n    elif (isTenantUser(user_role)):\n        iam_policy = __getPolicyForTenantUser(tenant_id, region, aws_account_id)\n    \n    return iam_policy\n\ndef __getPolicyForSystemAdmin(region, aws_account_id):\n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                    \"dynamodb:UpdateItem\",\n                    \"dynamodb:GetItem\",\n                    \"dynamodb:PutItem\",\n                    \"dynamodb:DeleteItem\",\n                    \"dynamodb:Query\",          \n                    \"dynamodb:Scan\"\n                  ],\n                  \"Resource\": [\n                       \"arn:aws:dynamodb:{0}:{1}:table/*\".format(region, aws_account_id),\n                  ]\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n    \ndef __getPolicyForTenantAdmin(tenant_id, sevice_identifier, region, aws_account_id):\n    if (sevice_identifier == utils.Service_Identifier.SHARED_SERVICES.value):\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:Query\"                   \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantUserMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantDetails\".format(region, aws_account_id)\n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringEquals\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"               \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantStackMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-Settings\".format(region, aws_account_id)\n                    ]\n                }\n            ]\n        }\n    else:\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"    \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"       \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                }\n            ]\n        }\n    return json.dumps(policy)\n\ndef __getPolicyForTenantUser(tenant_id, region, aws_account_id):\n    \n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"      \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              },\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"               \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n\n"
  },
  {
    "path": "Lab5/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.error (log_message)\n\n\"\"\"Log with tenant context. Extracts tenant context from the lambda events\n\"\"\"\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Lab5/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Lab5/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth\npython-jose[cryptography]\naws_requests_auth"
  },
  {
    "path": "Lab5/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass TenantTier(Enum):\n    PLATINUM    = \"Platinum\"\n    PREMIUM     = \"Premium\"\n    STANDARD    = \"Standard\"\n    BASIC       = \"Basic\"\n\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \nclass Service_Identifier(Enum):\n    SHARED_SERVICES     = \"SharedServices\"\n    BUSINESS_SERVICES    = \"BusinessServices\"\n\ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef create_notfound_response(message):\n    return {\n        \"statusCode\": StatusCodes.NOT_FOUND.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth                   \n\ndef get_headers(event):\n    return event['headers']\n\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\n\n\n\n"
  },
  {
    "path": "Lab5/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]\n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/provisioning\"\n                   ]\n                  ] \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/provisioning/{tenantid}\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          /provisioning:\n            post:\n              summary: provisions resource for new tenant\n              description: provisions resource for new tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref ProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /provisioning/{tenantid}:\n            put:\n              summary: deprovision by tenant\n              description: deprovision by tenant\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:                \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /tenant/init/{tenantname}:\n            get:\n              summary: Returns a tenant config\n              description: Return a tenant config by a tenant name\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantConfigFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock        \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            api_key:\n              type: \"apiKey\"\n              name: \"x-api-key\"\n              in: \"header\"     \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n\nOutputs:\n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Lab5/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantConfigLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantConfigFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]    "
  },
  {
    "path": "Lab5/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\"\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"  \nResources:\n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]  \n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:\n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Lab5/server/nested_templates/custom_resources.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableName:\n    Type: String\n  TenantStackMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  UpdateSettingsTableFunctionArn:\n    Type: String\n  UpdateTenantStackMapTableFunctionArn:\n    Type: String\n  CognitoUserPoolId:\n    Type: String\n  CognitoUserPoolClientId:\n    Type: String\nResources:\n  #Custom resources\n  \n  UpdateSettingsTable:\n    Type: Custom::UpdateSettingsTable\n    Properties:\n      ServiceToken: !Ref UpdateSettingsTableFunctionArn\n      SettingsTableName: !Ref ServerlessSaaSSettingsTableName\n      cognitoUserPoolId: !Ref CognitoUserPoolId\n      cognitoUserPoolClientId: !Ref CognitoUserPoolClientId\n  \n  \n  UpdateTenantStackMap:\n    Type: Custom::UpdateTenantStackMap\n    Properties:\n      ServiceToken: !Ref UpdateTenantStackMapTableFunctionArn\n      TenantStackMappingTableName: !Ref TenantStackMappingTableName"
  },
  {
    "path": "Lab5/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String\n  TenantDetailsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  TenantStackMappingTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  #Tenant Authorizer\n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  AuthorizerAccessRole:\n    Type: AWS::IAM::Role\n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      RoleName: authorizer-access-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS:\n                - !GetAtt 'AuthorizerExecutionRole.Arn'\n            Action:\n              - sts:AssumeRole       \n      Policies:\n        - PolicyName: authorizer-access-role-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:BatchGetItem     \n                  - dynamodb:GetItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Query\n                  - dynamodb:Scan     \n                Resource:  \n                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*\n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerAccessRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n        \n          \n  \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          TENANT_USER_POOL_CALLBACK_URL: !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"   \n\n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"\n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n\n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n         \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n        \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n             \n  \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']] \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem                  \n                Resource:\n                  - !Ref ServerlessSaaSSettingsTableArn                 \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n          \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n          \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n                 \n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"\n                 \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n      \n  GetTenantConfigFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.load_tenant_config\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n                  \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n  #Tenant Provisioning\n  ProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-provisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-provisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem      \n                  - dynamodb:DeleteItem                  \n                Resource:\n                  - !Ref TenantStackMappingTableArn\n              - Effect: Allow\n                Action:\n                  - codepipeline:StartPipelineExecution\n                Resource:\n                  - !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:serverless-saas-pipeline\n              - Effect: Allow\n                Action:\n                  - cloudformation:DeleteStack\n                Resource: \"*\"                        \n  ProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.provision_tenant\n      Runtime: python3.9\n      Role: !GetAtt ProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n        \n  DeProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-deprovisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-deprovisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            #Since this lambda is invoking cloudformation which is inturn removing AWS resources, we are giving overly permissive permissions to this lambda. \n            #You can limit this based upon your use case and AWS Resources you need to remove.\n            Statement: \n              - Effect: Allow\n                Action: \"*\"                  \n                Resource: \"*\"                     \n  DeProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: DeProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.deprovision_tenant\n      Runtime: python3.9\n      Role: !GetAtt DeProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n         \n  UpdateSettingsTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-settingstable-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-settingstable-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref ServerlessSaaSSettingsTableArn\n  UpdateSettingsTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateSettingsTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_settings_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateSettingsTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantStackMapTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-tenantstackmap-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-tenantstackmap-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref TenantStackMappingTableArn\n  UpdateTenantStackMapTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantStackMapTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_tenantstackmap_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantStackMapTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers        \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ProvisionTenantFunctionArn: \n    Value: !GetAtt ProvisionTenantFunction.Arn\n  DeProvisionTenantFunctionArn: \n    Value: !GetAtt DeProvisionTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantConfigFunctionArn:\n    Value: !GetAtt GetTenantConfigFunction.Arn  \n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  AuthorizerExecutionRoleArn:\n    Value: !GetAtt AuthorizerExecutionRole.Arn      \n  UpdateSettingsTableFunctionArn:\n    Value: !GetAtt UpdateSettingsTableFunction.Arn    \n  UpdateTenantStackMapTableFunctionArn:\n    Value: !GetAtt UpdateTenantStackMapTableFunction.Arn\n  "
  },
  {
    "path": "Lab5/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  ServerlessSaaSSettingsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: settingName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: settingName\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-Settings\n  TenantStackMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantStackMapping\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE      \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL\nOutputs:\n  ServerlessSaaSSettingsTableArn: \n    Value: !GetAtt ServerlessSaaSSettingsTable.Arn\n  ServerlessSaaSSettingsTableName: \n    Value: !Ref ServerlessSaaSSettingsTable\n  TenantStackMappingTableArn: \n    Value: !GetAtt TenantStackMappingTable.Arn\n  TenantStackMappingTableName: \n    Value: !Ref TenantStackMappingTable\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Lab5/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n\n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'       \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName    \n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName   \n    Condition: IsNotRunningInEventEngine\n"
  },
  {
    "path": "Lab5/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab5/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\" \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]   \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter] \n        \n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n  #setup custom resources\n  CustomResources:\n    Type: AWS::Serverless::Application\n    DependsOn: APIs    \n    Properties:\n      Location: nested_templates/custom_resources.yaml\n      Parameters:\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn  \n        ServerlessSaaSSettingsTableName: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableName\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        UpdateSettingsTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateSettingsTableFunctionArn\n        UpdateTenantStackMapTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantStackMapTableFunctionArn\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId      \n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolId:\n    Description: The user pool id of Admin Management userpool \n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolId\"  \n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolClientId\"\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  \n  AuthorizerExecutionRoleArn:\n    Description: The Lambda authorizer execution role\n    Value: !GetAtt LambdaFunctions.Outputs.AuthorizerExecutionRoleArn \n    Export:\n      Name: \"Serverless-SaaS-AuthorizerExecutionRoleArn\"   "
  },
  {
    "path": "Lab5/server/tenant-buildspec.yml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nversion: 0.2\nphases:\n  install:    \n    runtime-versions:\n      python: 3.9\n    commands:\n      # Install packages or any pre-reqs in this phase.\n      # Upgrading SAM CLI to 1.33.0 version\n      - python -m pip install aws-sam-cli==1.33.0\n      - sam --version\n      # Installing project dependencies\n      - cd Lab5/server/ProductService\n      - python -m pip install -r requirements.txt \n      - cd ../OrderService\n      - python -m pip install -r requirements.txt \n      \n\n  pre_build:\n    commands:\n      # Run tests, lint scripts or any other pre-build checks.\n      - cd ..\n      - export PYTHONPATH=./ProductService/\n      # unit tests needs to be fixed. Commenting for now\n      #- python -m pytest tests/unit/ProductService-test_handler.py\n\n  build:\n    commands:\n      # Use Build phase to build your artifacts (compile, etc.)\n      - sam build -t tenant-template.yaml\n\n  post_build:\n    commands:\n      # Use Post-Build for notifications, git tags, upload artifacts to S3\n      - sam package --s3-bucket $PACKAGE_BUCKET --output-template-file packaged.yaml\n\nartifacts:\n  discard-paths: yes\n  files:\n    # List of local artifacts that will be passed down the pipeline\n    - Lab5/server/packaged.yaml"
  },
  {
    "path": "Lab5/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\ncached=\"true\"\nparallel=\"true\"\n"
  },
  {
    "path": "Lab5/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  TenantIdParameter:\n    Type: String\n    Default: pooled\n    Description: Tenant ID for the stack\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\nConditions:\n  IsPooledDeploy: !Equals [ !Ref TenantIdParameter, pooled]\n  IsSiloDeploy: !Not [!Equals [ !Ref TenantIdParameter, pooled]]    \nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: !Join ['-', [serverless-saas-dependencies, !Ref TenantIdParameter]]\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Product, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Order, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  ProductFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, product-function-policy]]\n      Roles: \n        - !Ref ProductFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt ProductTable.Arn\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, product-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  OrderFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, order-function-policy]]\n      Roles: \n        - !Ref OrderFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt OrderTable.Arn\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, order-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n        \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !ImportValue Serverless-SaaS-AuthorizerExecutionRoleArn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolClientId\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Join ['-', [/aws/api-gateway/access-logs-serverless-saas-tenant-api-, !Ref TenantIdParameter]]\n      RetentionInDays: 30\n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join ['-', [!Ref TenantIdParameter, 'serverless-saas-tenant-api']]\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n  UpdateTenantApiGatewayUrlLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    DependsOn: ApiGatewayTenantApi\n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exec-role]]\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exe-policy ]]\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-Settings \n              - Effect: Allow\n                Action:\n                  - dynamodb:UpdateItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-TenantDetails    \n  UpdateTenantApiGatewayUrlFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantApiGatewayUrlLambdaExecutionRole\n    Properties:\n      CodeUri: custom_resources/\n      Handler: update_tenant_apigatewayurl.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantApiGatewayUrlLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantApiGatewayUrl:\n    Type: Custom::UpdateTenantApiGatewayUrl\n    DependsOn: UpdateTenantApiGatewayUrlFunction\n    Properties:\n      ServiceToken: !GetAtt UpdateTenantApiGatewayUrlFunction.Arn\n      TenantDetailsTableName: ServerlessSaaS-TenantDetails\n      SettingsTableName: ServerlessSaaS-Settings  \n      TenantId: !Ref TenantIdParameter  \n      TenantApiGatewayUrl: !Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/prod/\"    \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Lab6/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab6/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab6/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab6/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab6/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab6/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab6/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab6/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab6/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Lab6/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab6/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab6/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab6/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab6/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab6/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab6/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab6/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Lab6/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab6/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab6/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab6/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab6/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab6/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab6/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab6/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Lab6/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Lab6/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Lab6/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Lab6/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Lab6/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab6/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Lab6/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab6/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Lab6/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Lab6/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle>Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid\"\n              >\n                Submit\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {}\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n\n    if (localStorage.getItem('tenantName')) {\n      this.router.navigate(['/dashboard']);\n    }\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a\n              class=\"link\"\n              routerLink=\"/orders/detail/{{ element.shardId }}:{{\n                element.orderId\n              }}\"\n            >\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card\n        *ngIf=\"isLoading\"\n        style=\"display: flex; justify-content: center; align-items: center\"\n      >\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm!\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Description</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"description\"\n            placeholder=\"Enter product description\"\n          />\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button mat-raised-button color=\"primary\" (click)=\"(submit)\">\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ""
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup | undefined;\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private route: ActivatedRoute,\n    private router: Router,\n    private productSvc: ProductService,\n    private fb: FormBuilder\n  ) {}\n\n  ngOnInit(): void {\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.productForm = this.fb.group({\n      productId: [''],\n      name: [''],\n      price: [''],\n      description: [''],\n    });\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.name }}</td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Lab6/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab6/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab6/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  regApiGatewayUrl:\n    'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab6/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab6/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab6/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab6/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab6/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab6/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Lab6/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab6/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab6/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab6/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Lab6/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Lab6/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Lab6/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Lab6/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Lab6/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Lab6/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Lab6/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Lab6/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Lab6/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab6/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Lab6/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Lab6/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Lab6/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Lab6/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Lab6/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Lab6/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Lab6/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab6/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Lab6/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Lab6/scripts/deployment.sh",
    "content": "\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\necho \"server code is getting deployed\"\ncd ../server\nREGION=$(aws configure get region)\n\necho \"Validating server code using pylint\"\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\" -not -path \"./TenantPipeline/node_modules/*\")\nif [[ $? -ne 0 ]]; then\n  echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n  exit 1\nfi\n\nsam build -t shared-template.yaml --use-container\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n  sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\nelse\n  sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\nfi\n    \n\necho \"Pooled tenant server code is getting deployed\"\nREGION=$(aws configure get region)\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml --region=$REGION\ncd ../scripts\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\"\n  \n\n\n\n\n\n\n"
  },
  {
    "path": "Lab6/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Lab6/scripts/test-basic-tier-throttling.sh",
    "content": "#!/bin/bash\nAPP_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name stack-pooled --query \"Stacks[0].Outputs[?OutputKey=='TenantAPI'].OutputValue\" --output text)\n\nget_product() {\n   \n  STATUS_CODE=$(curl -s -o /dev/null -w '%{http_code}' -X GET -H \"Authorization: Bearer $1\" -H \"Content-Type: application/json\" $APP_APIGATEWAYURL/products)\n  \n  echo \"STATUS_CODE : $STATUS_CODE\";\n  \n}\n\nfor i in $(seq 1 1000)\ndo\n  get_product $1 $i &\ndone\nwait\necho \"All done\""
  },
  {
    "path": "Lab6/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Lab6/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Lab6/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab6/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nimport metrics_manager\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n \n\ndef get_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n\n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    table = __get_dynamodb_table(event, dynamodb)\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" Determine the table name based upo pooled vs silo model\n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )        \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Lab6/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab6/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Lab6/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Lab6/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport metrics_manager\nimport random\nimport threading\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']    \n    table = __get_dynamodb_table(event, dynamodb)\n\n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, \n                ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):    \n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):    \n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )       \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n"
  },
  {
    "path": "Lab6/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Lab6/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml\n\n"
  },
  {
    "path": "Lab6/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Lab6/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\napi_key_operation_user = os.environ['OPERATION_USERS_API_KEY']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n        api_key = api_key_operation_user\n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']    \n        api_key = tenant_details['Item']['apiKey']    \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n        \n\n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.SHARED_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'apiKey': api_key,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    authResponse['usageIdentifierKey'] = api_key\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab6/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\napi_key_operation_user = os.environ['OPERATION_USERS_API_KEY']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user   \n        api_key = api_key_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']\n        apigateway_url = tenant_details['Item']['apiGatewayUrl']\n        #TODO: Get API Key from tenant management table\n        #api_key = tenant_details['Item']['apiKey']\n        \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    if (auth_manager.isSaaSProvider(user_role) == False):\n        if (isTenantAuthorizedForThisAPI(apigateway_url, api_gateway_arn_tmp[0]) == False):\n            logger.error('Unauthorized')\n            raise Exception('Unauthorized')\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.BUSINESS_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        #TODO: Assign API Key to authorizer response\n        #'apiKey': api_key,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    authResponse['usageIdentifierKey'] = api_key\n    \n    return authResponse\n\ndef isTenantAuthorizedForThisAPI(apigateway_url, current_api_id):\n    if(apigateway_url.split('.')[0] != 'https://' + current_api_id):\n        return False\n    else:\n        return True\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Lab6/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Lab6/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport metrics_manager\nimport auth_manager\nimport requests\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n\nregion = os.environ['AWS_REGION']\n\n#This method has been locked down to be only\ndef create_tenant(event, context):\n    api_gateway_url = ''       \n    tenant_details = json.loads(event['body'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    table_system_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n    try:          \n        # for pooled tenants the apigateway url is saving in settings during stack creation\n        # update from there during tenant creation\n        if(tenant_details['dedicatedTenancy'].lower()!= 'true'):\n            settings_response = table_system_settings.get_item(\n                Key={\n                    'settingName': 'apiGatewayUrl-Pooled'\n                } \n            )\n            api_gateway_url = settings_response['Item']['settingValue']\n\n        #TODO: Save API Key inside the table**\n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],\n                    #'apiKey': tenant_details['apiKey'],\n                    'userPoolId': tenant_details['userPoolId'],                 \n                    'appClientId': tenant_details['appClientId'],\n                    'dedicatedTenancy': tenant_details['dedicatedTenancy'],\n                    'isActive': True,\n                    'apiGatewayUrl': api_gateway_url\n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n\n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        exiting_tenant_details = table_tenant_details.get_item(\n                Key={\n                    'tenantId': tenant_id,\n                }    \n            )             \n\n        if (exiting_tenant_details['Item']['tenantTier'].upper() != tenant_details['tenantTier'].upper()):\n            api_key = __getApiKey(tenant_details['tenantTier'])\n        else:            \n            api_key = exiting_tenant_details['Item']['apiKey']\n\n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier, apiKey=:apiKey\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier'],\n                    ':apiKey': api_key\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    url_deprovision_tenant = os.environ['DEPROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            update_user_response = __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, url_deprovision_tenant)\n\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()    \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    url_provision_tenant = os.environ['PROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            provision_response = __invoke_provision_tenant(update_details, headers, auth, host, stage_name, url_provision_tenant)\n            logger.log_with_tenant_context(event, provision_response)\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()    \n\ndef load_tenant_config(event, context):\n    params = event['pathParameters']\n    tenantName = urllib.parse.unquote(params['tenantname'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    try:\n        response = table_tenant_details.query(\n            IndexName=\"ServerlessSaas-TenantConfig\",\n            KeyConditionExpression=Key('tenantName').eq(tenantName),\n            ProjectionExpression=\"userPoolId, appClientId, apiGatewayUrl\"\n        ) \n    except Exception as e:\n        raise Exception('Error getting tenant config', e)\n    else:\n        if (response['Count'] == 0):\n            return utils.create_notfound_response(\"Tenant not found.\"+\n            \"Please enter exact tenant name used during tenant registration.\")\n        else:\n            return utils.generate_response(response['Items'][0])        \n\ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url + update_details['tenantId']])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while deprovisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while deprovisioning tenant')\n        raise Exception('Error occured while deprovisioning tenant', e) \n    else:\n        return \"Success invoking deprovision tenant\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\ndef __invoke_provision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.post(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while provisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while provisioning tenant')\n        raise Exception('Error occured while provisioning tenant', e) \n    else:\n        return \"Success invoking provision tenant\"\n\ndef __getApiKey(tenant_tier):\n    if (tenant_tier.upper() == utils.TenantTier.PLATINUM.value.upper()):\n        return os.environ['PLATINUM_TIER_API_KEY']\n    elif (tenant_tier.upper() == utils.TenantTier.PREMIUM.value.upper()):\n        return os.environ['PREMIUM_TIER_API_KEY']\n    elif (tenant_tier.upper() == utils.TenantTier.STANDARD.value.upper()):\n        return os.environ['STANDARD_TIER_API_KEY']\n    elif (tenant_tier.upper() == utils.TenantTier.BASIC.value.upper()):\n        return os.environ['BASIC_TIER_API_KEY']\n        \ndef __getTenantManagementTable(event):\n    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n    dynamodb = boto3.resource('dynamodb', aws_access_key_id=accesskey, aws_secret_access_key=secretkey, aws_session_token=sessiontoken)\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    return table_tenant_details\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Lab6/server/TenantManagementService/tenant-provisioning.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport os\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\ntenant_stack_mapping_table_name = os.environ['TENANT_STACK_MAPPING_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ncodepipeline = boto3.client('codepipeline')\ncloudformation = boto3.client('cloudformation')\ntable_tenant_stack_mapping = dynamodb.Table(tenant_stack_mapping_table_name)\n\nstack_name = 'stack-{0}'\n@tracer.capture_lambda_handler\ndef provision_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'stackName': stack_name.format(tenant_details['tenantId']),\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_codepipeline = codepipeline.start_pipeline_execution(\n            name='serverless-saas-pipeline'\n        )\n\n        logger.info(response_ddb)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Provisioning Started\")\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef deprovision_tenant(event, context):\n    logger.info(\"Request received to deprovision a tenant\")\n    \n    tenantid_to_deprovision = event['tenantId']\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.delete_item(\n            Key={\n                    'tenantId': tenantid_to_deprovision                    \n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_cloudformation = cloudformation.delete_stack(\n            StackName=stack_name.format(tenantid_to_deprovision)\n        )\n\n        logger.info(response_cloudformation)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Deprovisioning Started\")\n\n \n"
  },
  {
    "path": "Lab6/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\nprovision_tenant_resource_path = os.environ['PROVISION_TENANT_RESOURCE_PATH']\n\nplatinum_tier_api_key = os.environ['PLATINUM_TIER_API_KEY']\npremium_tier_api_key = os.environ['PREMIUM_TIER_API_KEY']\nstandard_tier_api_key = os.environ['STANDARD_TIER_API_KEY']\nbasic_tier_api_key = os.environ['BASIC_TIER_API_KEY']\n\nlambda_client = boto3.client('lambda')\n\n\ndef register_tenant(event, context):\n    try:\n        api_key=''\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n        tenant_details['dedicatedTenancy'] = 'false'\n\n        #TODO: Pass relevant apikey to tenant_details object based upon tenant tier\n        if (tenant_details['tenantTier'].upper() == utils.TenantTier.PLATINUM.value.upper()):\n            tenant_details['dedicatedTenancy'] = 'true'\n        \n        tenant_details['tenantId'] = tenant_id\n        \n        \n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['userPoolId'] = create_user_response['message']['userPoolId']\n        tenant_details['appClientId'] = create_user_response['message']['appClientId']\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n        if (tenant_details['dedicatedTenancy'].upper() == 'TRUE'):\n            provision_tenant_response = __provision_tenant(tenant_details, headers, auth, host, stage_name)\n            logger.info(provision_tenant_response)\n\n        \n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\ndef __provision_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, provision_tenant_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()['message']\n    except Exception as e:\n        logger.error('Error occured while provisioning the tenant')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Lab6/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport utils\nimport metrics_manager\nimport auth_manager\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\ndef create_tenant_admin_user(event, context):\n    tenant_user_pool_id = os.environ['TENANT_USER_POOL_ID']\n    tenant_app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    \n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    if (tenant_details['dedicatedTenancy'] == 'true'):\n        user_pool_response = user_mgmt.create_user_pool(tenant_id)\n        user_pool_id = user_pool_response['UserPool']['Id']\n        logger.info (user_pool_id)\n        \n        app_client_response = user_mgmt.create_user_pool_client(user_pool_id)\n        logger.info(app_client_response)\n        app_client_id = app_client_response['UserPoolClient']['ClientId']\n        user_pool_domain_response = user_mgmt.create_user_pool_domain(user_pool_id, tenant_id)\n        \n        logger.info (\"New Tenant Created\")\n    else:\n        user_pool_id = tenant_user_pool_id\n        app_client_id = tenant_app_client_id\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    \n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']    \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user\")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                    \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n\n\n\n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']      \n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            logger.log_with_tenant_context(event, \"Request completed to get new user\")\n            return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")         \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)            \n            response = client.admin_update_user_attributes(\n                Username=user_name,\n                UserPoolId=user_pool_id,\n                UserAttributes=[\n                    {\n                        'Name': 'email',\n                        'Value': user_details['userEmail']\n                    },\n                    {\n                        'Name': 'custom:userRole',\n                        'Value': user_details['userRole'] \n                    }\n                ]\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to update user\")\n            return utils.create_success_response(\"user updated\")    \n\n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n            response = client.admin_disable_user(\n                Username=user_name,\n                UserPoolId=user_pool_id\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n            return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info\n\nclass UserManagement:\n    def create_user_pool(self, tenant_id):\n        application_site_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        email_message = ''.join([\"Login into tenant UI application at \", \n                        application_site_url,\n                        \" with username {username} and temporary password {####}\"])\n        email_subject = \"Your temporary password for tenant UI application\"  \n        response = client.create_user_pool(\n            PoolName= tenant_id + '-ServerlessSaaSUserPool',\n            AutoVerifiedAttributes=['email'],\n            AccountRecoverySetting={\n                'RecoveryMechanisms': [\n                    {\n                        'Priority': 1,\n                        'Name': 'verified_email'\n                    },\n                ]\n            },\n            Schema=[\n                {\n                    'Name': 'email',\n                    'AttributeDataType': 'String',\n                    'Required': True,                    \n                },\n                {\n                    'Name': 'tenantId',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                },            \n                {\n                    'Name': 'userRole',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                }\n            ],\n            AdminCreateUserConfig={\n                'InviteMessageTemplate': {\n                    'EmailMessage': email_message,\n                    'EmailSubject': email_subject\n                }\n            }\n        )    \n        return response\n\n    def create_user_pool_client(self, user_pool_id):\n        user_pool_callback_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        response = client.create_user_pool_client(\n            UserPoolId= user_pool_id,\n            ClientName= 'ServerlessSaaSClient',\n            GenerateSecret= False,\n            AllowedOAuthFlowsUserPoolClient= True,\n            AllowedOAuthFlows=[\n                'code', 'implicit'\n            ],\n            SupportedIdentityProviders=[\n                'COGNITO',\n            ],\n            CallbackURLs=[\n                user_pool_callback_url,\n            ],\n            LogoutURLs= [\n                user_pool_callback_url,\n            ],\n            AllowedOAuthScopes=[\n                'email',\n                'openid',\n                'profile'\n            ],\n            WriteAttributes=[\n                'email',\n                'custom:tenantId'\n            ]\n        )\n        return response\n\n    def create_user_pool_domain(self, user_pool_id, tenant_id):\n        response = client.create_user_pool_domain(\n            Domain= tenant_id + '-serverlesssaas',\n            UserPoolId=user_pool_id\n        )\n        return response\n\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Lab6/server/TenantPipeline/.gitignore",
    "content": "*.js\n!jest.config.js\n*.d.ts\nnode_modules\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n\n# Parcel default cache directory\n.parcel-cache\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/.npmignore",
    "content": "*.ts\n!*.d.ts\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/README.md",
    "content": "# Welcome to your CDK TypeScript project!\n\nThis is a blank project for TypeScript development with CDK.\n\nThe `cdk.json` file tells the CDK Toolkit how to execute your app.\n\n## Useful commands\n\n * `npm run build`   compile typescript to js\n * `npm run watch`   watch for changes and compile\n * `npm run test`    perform the jest unit tests\n * `cdk deploy`      deploy this stack to your default AWS account/region\n * `cdk diff`        compare deployed stack with current state\n * `cdk synth`       emits the synthesized CloudFormation template\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/bin/pipeline.ts",
    "content": "#!/usr/bin/env node\nimport 'source-map-support/register';\nimport * as cdk from 'aws-cdk-lib';\nimport { ServerlessSaaSStack } from '../lib/serverless-saas-stack';\n\nconst app = new cdk.App();\nnew ServerlessSaaSStack(app, 'serverless-saas-pipeline');\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/cdk.json",
    "content": "{\n  \"app\": \"npx ts-node bin/pipeline.ts\",\n  \"context\": {}\n}\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/jest.config.js",
    "content": "module.exports = {\n  roots: ['<rootDir>/test'],\n  testMatch: ['**/*.test.ts'],\n  transform: {\n    '^.+\\\\.tsx?$': 'ts-jest'\n  }\n};\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/lib/serverless-saas-stack.ts",
    "content": "// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: MIT-0\n\nimport { Construct } from 'constructs';\nimport * as cdk from 'aws-cdk-lib';\n\nimport * as s3 from 'aws-cdk-lib/aws-s3';\nimport * as codecommit from 'aws-cdk-lib/aws-codecommit';\nimport * as codepipeline from 'aws-cdk-lib/aws-codepipeline';\nimport * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';\nimport * as codebuild from 'aws-cdk-lib/aws-codebuild';\n\nimport { Function, Runtime, AssetCode } from 'aws-cdk-lib/aws-lambda';\nimport { PolicyStatement } from 'aws-cdk-lib/aws-iam';\nimport { Duration } from 'aws-cdk-lib';\n\n\nexport class ServerlessSaaSStack extends cdk.Stack {\n  constructor(scope: Construct, id: string, props?: cdk.StackProps) {\n    super(scope, id, props);\n\n    const artifactsBucket = new s3.Bucket(this, \"ArtifactsBucket\", {\n      encryption: s3.BucketEncryption.S3_MANAGED,\n    });\n\n    //Since this lambda is invoking cloudformation which is inturn deploying AWS resources, we are giving overly permissive permissions to this lambda. \n    //You can limit this based upon your use case and AWS Resources you need to deploy.\n    const lambdaPolicy = new PolicyStatement()\n        lambdaPolicy.addActions(\"*\")\n        lambdaPolicy.addResources(\"*\")\n\n    const lambdaFunction = new Function(this, \"deploy-tenant-stack\", {\n        handler: \"lambda-deploy-tenant-stack.lambda_handler\",\n        runtime: Runtime.PYTHON_3_9,\n        code: new AssetCode(`./resources`),\n        memorySize: 512,\n        timeout: Duration.seconds(10),\n        environment: {\n            BUCKET: artifactsBucket.bucketName,\n        },\n        initialPolicy: [lambdaPolicy],\n    })\n\n    // Pipeline creation starts\n    const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {\n      pipelineName: 'serverless-saas-pipeline',\n      artifactBucket: artifactsBucket\n    });\n\n    // Import existing CodeCommit sam-app repository\n    const codeRepo = codecommit.Repository.fromRepositoryName(\n      this,\n      'AppRepository', \n      'aws-serverless-saas-workshop'\n    );\n\n    // Declare source code as an artifact\n    const sourceOutput = new codepipeline.Artifact();\n\n    // Add source stage to pipeline\n    pipeline.addStage({\n      stageName: 'Source',\n      actions: [\n        new codepipeline_actions.CodeCommitSourceAction({\n          actionName: 'CodeCommit_Source',\n          repository: codeRepo,\n          branch: 'main',\n          output: sourceOutput,\n          variablesNamespace: 'SourceVariables'\n        }),\n      ],\n    });\n\n    // Declare build output as artifacts\n    const buildOutput = new codepipeline.Artifact();\n\n\n\n    //Declare a new CodeBuild project\n    const buildProject = new codebuild.PipelineProject(this, 'Build', {\n      buildSpec : codebuild.BuildSpec.fromSourceFilename(\"Lab6/server/tenant-buildspec.yml\"),\n      environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4 },\n      environmentVariables: {\n        'PACKAGE_BUCKET': {\n          value: artifactsBucket.bucketName\n        }\n      }\n    });\n\n    \n\n    // Add the build stage to our pipeline\n    pipeline.addStage({\n      stageName: 'Build',\n      actions: [\n        new codepipeline_actions.CodeBuildAction({\n          actionName: 'Build-Serverless-SaaS',\n          project: buildProject,\n          input: sourceOutput,\n          outputs: [buildOutput],\n        }),\n      ],\n    });\n\n    const deployOutput = new codepipeline.Artifact();\n\n\n    //Add the Lambda function that will deploy the tenant stack in a multitenant way\n    pipeline.addStage({\n      stageName: 'Deploy',\n      actions: [\n        new codepipeline_actions.LambdaInvokeAction({\n          actionName: 'DeployTenantStack',\n          lambda: lambdaFunction,\n          inputs: [buildOutput],\n          outputs: [deployOutput],\n          userParameters: {\n            'artifact': 'Artifact_Build_Build-Serverless-SaaS',\n            'template_file': 'packaged.yaml',\n            'commit_id': '#{SourceVariables.CommitId}'\n          }\n        }),\n      ],\n    });    \n  }\n}\n"
  },
  {
    "path": "Lab6/server/TenantPipeline/package.json",
    "content": "\n{\n  \"name\": \"pipeline\",\n  \"version\": \"0.1.0\",\n  \"bin\": {\n    \"pipeline\": \"bin/pipeline.js\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc -w\",\n    \"test\": \"jest\",\n    \"cdk\": \"cdk\"\n  },\n  \"devDependencies\": {\n    \"@aws-cdk/assert\": \"1.64.1\",\n    \"@types/jest\": \"^26.0.10\",\n    \"@types/node\": \"10.17.27\",\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"node-notifier\": \"^8.0.1\",\n    \"ts-jest\": \"^26.2.0\",\n    \"ts-node\": \"^8.1.0\",\n    \"typescript\": \"4.9.5\",\n    \"@types/prettier\": \"2.6.0\"\n  },\n  \"dependencies\": {\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"source-map-support\": \"^0.5.19\"\n  }\n}"
  },
  {
    "path": "Lab6/server/TenantPipeline/resources/lambda-deploy-tenant-stack.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom boto3.session import Session\n\nimport json\nimport boto3\nimport zipfile\nimport tempfile\nimport botocore\nimport traceback\nimport time\n\n\n\nprint('Loading function')\n\ncf = boto3.client('cloudformation')\ncode_pipeline = boto3.client('codepipeline')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_stack_mapping = dynamodb.Table('ServerlessSaaS-TenantStackMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\ntable_tenant_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n\ndef find_artifact(artifacts, name):\n    \"\"\"Finds the artifact 'name' among the 'artifacts'\n    \n    Args:\n        artifacts: The list of artifacts available to the function\n        name: The artifact we wish to use\n    Returns:\n        The artifact dictionary found\n    Raises:\n        Exception: If no matching artifact is found\n    \n    \"\"\"\n    for artifact in artifacts:\n        if artifact['name'] == name:\n            return artifact\n            \n    raise Exception('Input artifact named \"{0}\" not found in event'.format(name))\n\ndef get_template_url(s3, artifact, file_in_zip):\n    \"\"\"Gets the template artifact\n    \n    Downloads the artifact from the S3 artifact store to a temporary file\n    then extracts the zip and returns the file containing the CloudFormation\n    template.\n    \n    Args:\n        artifact: The artifact to download\n        file_in_zip: The path to the file within the zip containing the template\n        \n    Returns:\n        The CloudFormation template as a string\n        \n    Raises:\n        Exception: Any exception thrown while downloading the artifact or unzipping it\n    \n    \"\"\"\n    tmp_file = tempfile.NamedTemporaryFile()\n    bucket = artifact['location']['s3Location']['bucketName']\n    print(bucket)\n\n    key = artifact['location']['s3Location']['objectKey']\n    print(key)    \n    with tempfile.NamedTemporaryFile() as tmp_file:\n        s3.download_file(bucket, key, tmp_file.name)\n        with zipfile.ZipFile(tmp_file.name, 'r') as zip:\n            extracted_file = zip.extract(file_in_zip, '/tmp/')\n            s3.upload_file(extracted_file, bucket, file_in_zip)\n            template_url =''.join(['https://', bucket,'.s3.amazonaws.com/',file_in_zip])\n            return template_url  \n\n            \n   \ndef update_stack(stack, template_url, params):\n    \"\"\"Start a CloudFormation stack update\n    \n    Args:\n        stack: The stack to update\n        template_url: The template to apply\n        \n    Returns:\n        True if an update was started, false if there were no changes\n        to the template since the last update.\n        \n    Raises:\n        Exception: Any exception besides \"No updates are to be performed.\"\n    \n    \"\"\"\n    try:\n        cf.update_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n        return True\n        \n    except botocore.exceptions.ClientError as e:\n        if e.response['Error']['Message'] == 'No updates are to be performed.':\n            return False\n        else:\n            raise Exception('Error updating CloudFormation stack \"{0}\"'.format(stack), e)\n\ndef stack_exists(stack):\n    \"\"\"Check if a stack exists or not\n    \n    Args:\n        stack: The stack to check\n        \n    Returns:\n        True or False depending on whether the stack exists\n        \n    Raises:\n        Any exceptions raised .describe_stacks() besides that\n        the stack doesn't exist.\n        \n    \"\"\"\n    try:\n        cf.describe_stacks(StackName=stack)\n        return True\n    except botocore.exceptions.ClientError as e:\n        if \"does not exist\" in e.response['Error']['Message']:\n            return False\n        else:\n            raise e\n\ndef create_stack(stack, template_url, params):\n    \"\"\"Starts a new CloudFormation stack creation\n    \n    Args:\n        stack: The stack to be created\n        template_url: The template for the stack to be created with\n        \n    Throws:\n        Exception: Any exception thrown by .create_stack()\n    \"\"\"\n    cf.create_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n \ndef get_stack_status(stack):\n    \"\"\"Get the status of an existing CloudFormation stack\n    \n    Args:\n        stack: The name of the stack to check\n        \n    Returns:\n        The CloudFormation status string of the stack such as CREATE_COMPLETE\n        \n    Raises:\n        Exception: Any exception thrown by .describe_stacks()\n        \n    \"\"\"\n    stack_description = cf.describe_stacks(StackName=stack)\n    return stack_description['Stacks'][0]['StackStatus']\n  \ndef put_job_success(job, message):\n    \"\"\"Notify CodePipeline of a successful job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    print('Putting job success')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job)\n  \ndef put_job_failure(job, message):\n    \"\"\"Notify CodePipeline of a failed job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_failure_result()\n    \n    \"\"\"\n    print('Putting job failure')\n    print(message)\n    code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'})\n \ndef continue_job_later(job, message):\n    \"\"\"Notify CodePipeline of a continuing job\n    \n    This will cause CodePipeline to invoke the function again with the\n    supplied continuation token.\n    \n    Args:\n        job: The JobID\n        message: A message to be logged relating to the job status\n        continuation_token: The continuation token\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    \n    # Use the continuation token to keep track of any job execution state\n    # This data will be available when a new job is scheduled to continue the current execution\n    continuation_token = json.dumps({'previous_job_id': job})\n    \n    print('Putting job continuation')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job, continuationToken=continuation_token)\n\ndef start_update_or_create(job_id, stack, template_url, params):\n    \"\"\"Starts the stack update or create process\n    \n    If the stack exists then update, otherwise create.\n    \n    Args:\n        job_id: The ID of the CodePipeline job\n        stack: The stack to create or update\n        template_url: The template to create/update the stack with\n    \n    \"\"\"\n    if stack_exists(stack):\n        status = get_stack_status(stack)\n        if status not in ['CREATE_COMPLETE', 'ROLLBACK_COMPLETE', 'UPDATE_COMPLETE']:\n            # If the CloudFormation stack is not in a state where\n            # it can be updated again then fail the job right away.\n            put_job_failure(job_id, 'Stack cannot be updated when status is: ' + status)\n            return\n        \n        were_updates = update_stack(stack, template_url, params)\n        \n        if were_updates:\n            # If there were updates then continue the job so it can monitor\n            # the progress of the update.\n            continue_job_later(job_id, 'Stack update started')  \n            \n        else:\n            # If there were no updates then succeed the job immediately \n            put_job_success(job_id, 'There were no stack updates')    \n    else:\n        # If the stack doesn't already exist then create it instead\n        # of updating it.\n        create_stack(stack, template_url, params)\n        # Continue the job so the pipeline will wait for the CloudFormation\n        # stack to be created.\n        continue_job_later(job_id, 'Stack create started') \n\ndef check_stack_update_status(job_id, stack):\n    \"\"\"Monitor an already-running CloudFormation update/create\n    \n    Succeeds, fails or continues the job depending on the stack status.\n    \n    Args:\n        job_id: The CodePipeline job ID\n        stack: The stack to monitor\n    \n    \"\"\"\n    status = get_stack_status(stack)\n    if status in ['UPDATE_COMPLETE', 'CREATE_COMPLETE']:\n        # If the update/create finished successfully then\n        # succeed the job and don't continue.\n        put_job_success(job_id, 'Stack update complete')\n        \n    elif status in ['UPDATE_IN_PROGRESS', 'UPDATE_ROLLBACK_IN_PROGRESS', \n    'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', 'CREATE_IN_PROGRESS', \n    'ROLLBACK_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS']:\n        # If the job isn't finished yet then continue it\n        continue_job_later(job_id, 'Stack update still in progress') \n       \n    else:\n        # If the Stack is a state which isn't \"in progress\" or \"complete\"\n        # then the stack update/create has failed so end the job with\n        # a failed result.\n        put_job_failure(job_id, 'Update failed: ' + status)\n\ndef get_user_params(job_data):\n    \"\"\"Decodes the JSON user parameters and validates the required properties.\n    \n    Args:\n        job_data: The job data structure containing the UserParameters string which should be a valid JSON structure\n        \n    Returns:\n        The JSON parameters decoded as a dictionary.\n        \n    Raises:\n        Exception: The JSON can't be decoded or a property is missing.\n        \n    \"\"\"\n    try:\n        # Get the user parameters which contain the stack, artifact and file settings\n        user_parameters = job_data['actionConfiguration']['configuration']['UserParameters']\n        decoded_parameters = json.loads(user_parameters)\n            \n    except Exception:\n        # We're expecting the user parameters to be encoded as JSON\n        # so we can pass multiple values. If the JSON can't be decoded\n        # then fail the job with a helpful message.\n        raise Exception('UserParameters could not be decoded as JSON')\n    \n\n    if 'artifact' not in decoded_parameters:\n        # Validate that the artifact name is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the artifact name')\n    \n    if 'template_file' not in decoded_parameters:\n        # Validate that the template file is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the template file name')\n    \n    return decoded_parameters\n    \ndef setup_s3_client(job_data):\n    \"\"\"Creates an S3 client\n    \n    Uses the credentials passed in the event by CodePipeline. These\n    credentials can be used to access the artifact bucket.\n    \n    Args:\n        job_data: The job data structure\n        \n    Returns:\n        An S3 client with the appropriate credentials\n        \n    \"\"\"\n    # Could not use the artifact credentials to put object to artifacts s3 bucket.\n    # We are running into issue as described in https://github.com/aws/aws-cdk/issues/3274\n     \n    # key_id = job_data['artifactCredentials']['accessKeyId']\n    # key_secret = job_data['artifactCredentials']['secretAccessKey']\n    # session_token = job_data['artifactCredentials']['sessionToken']\n    \n    # session = Session(aws_access_key_id=key_id,\n    #     aws_secret_access_key=key_secret,\n    #     aws_session_token=session_token)\n    # return session.client('s3')\n    return boto3.client('s3')\n\ndef get_tenant_params(tenantId):\n    \"\"\"Get tenant details to be supplied to Cloud formation\n\n    Args:\n        tenantId (str): tenantId for which details are needed\n\n    Returns:\n        params from tenant management table\n    \"\"\"\n    params = []\n    param_tenantid = {}\n    param_tenantid['ParameterKey'] = 'TenantIdParameter'\n    param_tenantid['ParameterValue'] = tenantId\n    params.append(param_tenantid)\n\n    return params\n\ndef add_parameter(params, parameter_key, parameter_value):\n    parameter = {}\n    parameter['ParameterKey'] = parameter_key\n    parameter['ParameterValue'] = parameter_value\n    params.append(parameter)\n\n\n\n\ndef update_tenantstackmapping(tenantId, commit_id):\n    \"\"\"Update the tenant stack mapping table with the code pipeline job id\n\n    Args:\n        tenantId ([string]): tenant id for which data needs to be updated\n        job_id ([type]): current code pipeline job id\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    response = table_tenant_stack_mapping.update_item(\n            Key={'tenantId': tenantId},\n            UpdateExpression=\"set codeCommitId=:codeCommitId\",\n            ExpressionAttributeValues={\n            ':codeCommitId': commit_id\n            },\n            ReturnValues=\"NONE\") \n    \n    return response\n\ndef lambda_handler(event, context):\n    \"\"\"The Lambda function handler\n    \n    If a continuing job then checks the CloudFormation stack status\n    and updates the job accordingly.\n    \n    If a new job then kick of an update or creation of the target\n    CloudFormation stack.\n    \n    Args:\n        event: The event passed by Lambda\n        context: The context passed by Lambda\n        \n    \"\"\"\n    try:\n        # Extract the Job ID\n        job_id = event['CodePipeline.job']['id']\n        \n        # Extract the Job Data \n        job_data = event['CodePipeline.job']['data']\n        \n        # Extract the params\n        params = get_user_params(job_data)\n        \n        # Get the list of artifacts passed to the function\n        artifacts = job_data['inputArtifacts']\n        \n        artifact = params['artifact']\n        template_file = params['template_file']\n        commit_id = params['commit_id']\n\n        # Get all the stacks for each tenant to be updated/created from tenant stack mapping table\n        mappings = table_tenant_stack_mapping.scan()\n        print (mappings)\n        #Update/Create stacks for all tenants\n        for mapping in mappings['Items']:\n            stack = mapping['stackName']\n            tenantId = mapping['tenantId']\n            applyLatestRelease = mapping['applyLatestRelease']\n\n            if (applyLatestRelease):\n                # Get the parameters to be passed to the Cloudformation from tenant table\n                params = get_tenant_params(tenantId)\n                \n                if 'continuationToken' in job_data:\n                    # If we're continuing then the create/update has already been triggered\n                    # we just need to check if it has finished.\n                    check_stack_update_status(job_id, stack)\n                else:\n                    # Get the artifact details\n                    artifact_data = find_artifact(artifacts, artifact)\n                    # Get S3 client to access artifact with\n                    s3 = setup_s3_client(job_data)\n                    # Get the JSON template file out of the artifact\n                    template_url = get_template_url(s3, artifact_data, template_file)\n                    \n                    # Kick off a stack update or create\n                    start_update_or_create(job_id, stack, template_url, params)  \n\n                    # If we are applying the release, update tenant stack mapping with the pipe line id\n                    update_tenantstackmapping(tenantId, commit_id)\n    except Exception as e:\n        # If any other exceptions which we didn't expect are raised\n        # then fail the job and log the exception message.\n        print('Function failed due to exception.') \n        print(e)\n        traceback.print_exc()\n        put_job_failure(job_id, 'Function exception: ' + str(e))\n    \n    #put_job_success(job_id, \"Changeset executed successfully\")\n    print('Function complete.')   \n    return \"Complete.\""
  },
  {
    "path": "Lab6/server/TenantPipeline/test/pipeline.test.ts",
    "content": "// import { SynthUtils } from '@aws-cdk/assert';\n// import { Stack, App } from 'aws-cdk-lib';\n// import { Template } from 'aws-cdk-lib/assertions';\n// import * as Pipeline from '../lib/serverless-saas-stack';\n\n// test('synthesized cloudformation template should match original template', () => {\n//     const app = new App();\n//     const stack = new Pipeline.ServerlessSaaSStack(app, 'MyTestStack');\n//     const template = Template.fromStack(stack);\n//     expect(template).toMatchSnapshot();\n// });"
  },
  {
    "path": "Lab6/server/TenantPipeline/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"es2018\"],\n    \"declaration\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": false,\n    \"inlineSourceMap\": true,\n    \"inlineSources\": true,\n    \"experimentalDecorators\": true,\n    \"strictPropertyInitialization\": false,\n    \"typeRoots\": [\"./node_modules/@types\"]\n  },\n  \"exclude\": [\"cdk.out\"]\n}\n"
  },
  {
    "path": "Lab6/server/custom_resources/requirements.txt",
    "content": "requests\ncrhelper"
  },
  {
    "path": "Lab6/server/custom_resources/update_settings_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" Called as part of bootstrap template. \n        Inserts/Updates Settings table based upon the resources deployed inside bootstrap template\n        We use these settings inside tenant template\n\n    Args:\n            event ([type]): [description]\n            _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating settings\")\n\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    cognitoUserPoolId = event['ResourceProperties']['cognitoUserPoolId']\n    cognitoUserPoolClientId = event['ResourceProperties']['cognitoUserPoolClientId']\n\n    table_system_settings = dynamodb.Table(settings_table_name)\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'userPoolId-pooled',\n                    'settingValue' : cognitoUserPoolId\n                }\n            )\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'appClientId-pooled',\n                    'settingValue' : cognitoUserPoolClientId\n                }\n            )\n\n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab6/server/custom_resources/update_tenant_apigatewayurl.py",
    "content": "import json\nimport boto3\nimport logger\nfrom boto3.dynamodb.conditions import Key\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    client = boto3.client('dynamodb')\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" The URL for Tenant APIs(Product/Order) can differ by tenant.\n        For Pooled tenants it is shared and for Silo (Platinum tier tenants) it is unique to them.\n        This method keeps the URL for Pooled tenants inside Settings Table, since it is shared across multiple tenants,\n        And for Silo tenants inside the tenant management table along with other tenant settings, for that tenant\n\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Details table\")\n\n    tenant_details_table_name = event['ResourceProperties']['TenantDetailsTableName']\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    tenant_id = event['ResourceProperties']['TenantId']\n    tenant_api_gateway_url = event['ResourceProperties']['TenantApiGatewayUrl']\n\n\n    if(tenant_id.lower() =='pooled'):\n        # Note: Tenant management service will use below setting to update apiGatewayUrl for pooled tenants in TenantDetails table\n        settings_table = dynamodb.Table(settings_table_name)\n        settings_table.put_item(Item={\n                    'settingName': 'apiGatewayUrl-Pooled',\n                    'settingValue' : tenant_api_gateway_url                    \n                })\n        \n    else:\n        tenant_details = dynamodb.Table(tenant_details_table_name)\n        response = tenant_details.update_item(\n            Key={'tenantId': tenant_id},\n            UpdateExpression=\"set apiGatewayUrl=:apiGatewayUrl\",\n            ExpressionAttributeValues={\n            ':apiGatewayUrl': tenant_api_gateway_url\n            },\n            ReturnValues=\"NONE\") \n                   \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab6/server/custom_resources/update_tenantstackmap_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" One time entry for pooled tenants inside tenant stack mapping table.\n        This ensures that when code pipeline for tenant template is kicked off, it always create a default stack for pooled tenants.\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Stack Map\")\n\n    tenantstackmap_table_name = event['ResourceProperties']['TenantStackMappingTableName']\n    \n    table_stack_mapping = dynamodb.Table(tenantstackmap_table_name)\n    \n    response = table_stack_mapping.put_item(\n            Item={\n                    'tenantId': 'pooled',\n                    'stackName' : 'stack-pooled',\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )                  \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab6/server/custom_resources/update_usage_plan.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\n    apigateway = boto3.client('apigateway')\nexcept Exception as e:\n    helper.init_failure(e)\n\n@helper.create\ndef do_action(event, _):\n    \"\"\" Usage plans are created as part of bootstrap template.\n        This method associates the usage plans for various tiers with tenant Apis\n\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"adding api gateway stage to usage plan\")\n    api_id = event['ResourceProperties']['ApiGatewayId']\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    is_pooled_deploy = event['ResourceProperties']['IsPooledDeploy']\n    stage = event['ResourceProperties']['Stage']\n    usage_plan_id_basic = event['ResourceProperties']['UsagePlanBasicTier']\n    usage_plan_id_standard = event['ResourceProperties']['UsagePlanStandardTier']\n    usage_plan_id_premium = event['ResourceProperties']['UsagePlanPremiumTier']\n    usage_plan_id_platinum = event['ResourceProperties']['UsagePlanPlatinumTier']\n\n    table_system_settings = dynamodb.Table(settings_table_name)\n\n    if(is_pooled_deploy == \"true\"):\n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_basic,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n\n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_standard,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n        \n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_premium,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n        \n    else:\n        \n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_platinum,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n        \n@helper.update\n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Lab6/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\ndef getPolicyForUser(user_role, service_identifier, tenant_id, region, aws_account_id):\n    \"\"\" This method is being used by Authorizer to get appropriate policy by user role\n\n    Args:\n        user_role (string): UserRoles enum\n        tenant_id (string): \n        region (string): \n        aws_account_id (string):  \n\n    Returns:\n        string: policy that tenant needs to assume\n    \"\"\"\n    iam_policy = \"\"\n    \n    if (isSystemAdmin(user_role)):\n        iam_policy = __getPolicyForSystemAdmin(region, aws_account_id)\n    elif (isTenantAdmin(user_role)):\n        iam_policy = __getPolicyForTenantAdmin(tenant_id, service_identifier, region, aws_account_id)\n    elif (isTenantUser(user_role)):\n        iam_policy = __getPolicyForTenantUser(tenant_id, region, aws_account_id)\n    \n    return iam_policy\n\ndef __getPolicyForSystemAdmin(region, aws_account_id):\n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                    \"dynamodb:UpdateItem\",\n                    \"dynamodb:GetItem\",\n                    \"dynamodb:PutItem\",\n                    \"dynamodb:DeleteItem\",\n                    \"dynamodb:Query\",          \n                    \"dynamodb:Scan\"\n                  ],\n                  \"Resource\": [\n                       \"arn:aws:dynamodb:{0}:{1}:table/*\".format(region, aws_account_id),\n                  ]\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n    \ndef __getPolicyForTenantAdmin(tenant_id, sevice_identifier, region, aws_account_id):\n    if (sevice_identifier == utils.Service_Identifier.SHARED_SERVICES.value):\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:Query\"                   \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantUserMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantDetails\".format(region, aws_account_id)\n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringEquals\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"               \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantStackMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-Settings\".format(region, aws_account_id)\n                    ]\n                }\n            ]\n        }\n    else:\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"    \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"       \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                }\n            ]\n        }\n    return json.dumps(policy)\n\ndef __getPolicyForTenantUser(tenant_id, region, aws_account_id):\n    \n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"      \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              },\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"               \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n\n"
  },
  {
    "path": "Lab6/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.error (log_message)\n\n\"\"\"Log with tenant context. Extracts tenant context from the lambda events\n\"\"\"\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Lab6/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Lab6/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth\npython-jose[cryptography]\naws_requests_auth"
  },
  {
    "path": "Lab6/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass TenantTier(Enum):\n    PLATINUM    = \"Platinum\"\n    PREMIUM     = \"Premium\"\n    STANDARD    = \"Standard\"\n    BASIC       = \"Basic\"\n\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \nclass Service_Identifier(Enum):\n    SHARED_SERVICES     = \"SharedServices\"\n    BUSINESS_SERVICES    = \"BusinessServices\"\n\ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef create_notfound_response(message):\n    return {\n        \"statusCode\": StatusCodes.NOT_FOUND.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth                   \n\ndef get_headers(event):\n    return event['headers']\n\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\n\n\n\n"
  },
  {
    "path": "Lab6/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  ApiKeyOperationUsersParameter:\n    Type: String    \n  ApiKeyPlatinumTierParameter:\n    Type: String\n  ApiKeyPremiumTierParameter:\n    Type: String\n  ApiKeyStandardTierParameter:\n    Type: String\n  ApiKeyBasicTierParameter:\n    Type: String  \n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]\n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/provisioning\"\n                   ]\n                  ] \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/provisioning/{tenantid}\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          /provisioning:\n            post:\n              summary: provisions resource for new tenant\n              description: provisions resource for new tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref ProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /provisioning/{tenantid}:\n            put:\n              summary: deprovision by tenant\n              description: deprovision by tenant\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:                \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /tenant/init/{tenantname}:\n            get:\n              summary: Returns a tenant config\n              description: Return a tenant config by a tenant name\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantConfigFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock        \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:    \n                - api_key: [] \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - api_key: []       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:  \n                - api_key: []          \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            api_key:\n              type: \"apiKey\"\n              name: \"x-api-key\"\n              in: \"header\"     \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n #Create API Keys and Usage Plans\n  APIGatewayApiKeySystemAdmin:\n    Type: AWS::ApiGateway::ApiKey\n    Properties:\n      Description: \"This is the api key to be used by system admin\"\n      Enabled: True\n      Name: Serverless-SaaS-SysAdmin-ApiKey\n      Value: !Ref ApiKeyOperationUsersParameter\n  APIGatewayApiKeyPlatinumTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by platinum tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-PlatinumTier-ApiKey\n      Value: !Ref ApiKeyPlatinumTierParameter\n  APIGatewayApiKeyPremiumTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by premium tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-PremiumTier-ApiKey\n      Value: !Ref ApiKeyPremiumTierParameter\n  APIGatewayApiKeyStandardTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by standard tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-StandardTier-ApiKey\n      Value: !Ref ApiKeyStandardTierParameter\n  APIGatewayApiKeyBasicTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by basic tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-BasicTier-ApiKey\n      Value: !Ref ApiKeyBasicTierParameter\n\n  UsagePlanPlatinumTier:   \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for platinum tier tenants\n      Quota:\n        Limit: 10000\n        Period: DAY\n      Throttle:\n        BurstLimit: 300\n        RateLimit: 300\n      UsagePlanName: Plan_Platinum_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanPremiumTier:   \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for premium tier tenants\n      Quota:\n        Limit: 5000\n        Period: DAY\n      Throttle:\n        BurstLimit: 200\n        RateLimit: 100\n      UsagePlanName: Plan_Premium_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanStandardTier: \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for standard tier tenants\n      Quota:\n        Limit: 3000\n        Period: DAY\n      Throttle:\n        BurstLimit: 100\n        RateLimit: 75\n      UsagePlanName: Plan_Standard_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanBasicTier:   \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for basic tier tenants\n      Quota:\n        Limit: 500\n        Period: DAY\n      Throttle:\n        BurstLimit: 50\n        RateLimit: 50\n      UsagePlanName: Plan_Basic_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanSystemAdmin:\n    Type: \"AWS::ApiGateway::UsagePlan\"\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for system admin\n      Quota:\n        Limit: 10000\n        Period: DAY\n      Throttle:\n        BurstLimit: 5000\n        RateLimit: 500\n      UsagePlanName: System_Admin_Usage_Plan\n    DependsOn:\n      - AdminApiGatewayApiprodStage\n\n  AssociateAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties:\n      KeyId: !Ref APIGatewayApiKeySystemAdmin\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanSystemAdmin\n    DependsOn: UsagePlanSystemAdmin\n  AssociatePlatinumAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyPlatinumTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanPlatinumTier\n    DependsOn: UsagePlanPlatinumTier\n  AssociatePremiumAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyPremiumTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanPremiumTier\n    DependsOn: UsagePlanPremiumTier\n  AssociateStandardAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyStandardTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanStandardTier\n    DependsOn: UsagePlanStandardTier\n  AssociateBasicAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyBasicTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanBasicTier\n    DependsOn: UsagePlanBasicTier\nOutputs:\n  UsagePlanBasicTier: \n    Value: !Ref UsagePlanBasicTier\n  UsagePlanStandardTier: \n    Value: !Ref UsagePlanStandardTier\n  UsagePlanPremiumTier: \n    Value: !Ref UsagePlanPremiumTier\n  UsagePlanPlatinumTier: \n    Value: !Ref UsagePlanPlatinumTier\n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Lab6/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantConfigLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantConfigFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]    "
  },
  {
    "path": "Lab6/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\"\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"  \nResources:\n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]  \n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n        \n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:\n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Lab6/server/nested_templates/custom_resources.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableName:\n    Type: String\n  TenantStackMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  UpdateSettingsTableFunctionArn:\n    Type: String\n  UpdateTenantStackMapTableFunctionArn:\n    Type: String\n  CognitoUserPoolId:\n    Type: String\n  CognitoUserPoolClientId:\n    Type: String\nResources:\n  #Custom resources\n  \n  UpdateSettingsTable:\n    Type: Custom::UpdateSettingsTable\n    Properties:\n      ServiceToken: !Ref UpdateSettingsTableFunctionArn\n      SettingsTableName: !Ref ServerlessSaaSSettingsTableName\n      cognitoUserPoolId: !Ref CognitoUserPoolId\n      cognitoUserPoolClientId: !Ref CognitoUserPoolClientId\n  \n  \n  UpdateTenantStackMap:\n    Type: Custom::UpdateTenantStackMap\n    Properties:\n      ServiceToken: !Ref UpdateTenantStackMapTableFunctionArn\n      TenantStackMappingTableName: !Ref TenantStackMappingTableName"
  },
  {
    "path": "Lab6/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String\n  TenantDetailsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  ApiKeyOperationUsersParameter:\n    Type: String\n  ApiKeyPlatinumTierParameter:\n    Type: String\n  ApiKeyPremiumTierParameter:\n    Type: String\n  ApiKeyStandardTierParameter:\n    Type: String\n  ApiKeyBasicTierParameter:\n    Type: String  \n  TenantStackMappingTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  #Tenant Authorizer\n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  AuthorizerAccessRole:\n    Type: AWS::IAM::Role\n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      RoleName: authorizer-access-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS:\n                - !GetAtt 'AuthorizerExecutionRole.Arn'\n            Action:\n              - sts:AssumeRole       \n      Policies:\n        - PolicyName: authorizer-access-role-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:BatchGetItem     \n                  - dynamodb:GetItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Query\n                  - dynamodb:Scan     \n                Resource:  \n                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*\n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerAccessRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n          OPERATION_USERS_API_KEY : !Ref ApiKeyOperationUsersParameter\n        \n          \n  \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          TENANT_USER_POOL_CALLBACK_URL: !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"   \n\n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"\n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n\n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n         \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n        \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n             \n  \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']] \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem                  \n                Resource:\n                  - !Ref ServerlessSaaSSettingsTableArn                 \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n          \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n          \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n                 \n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"\n          PLATINUM_TIER_API_KEY: !Ref ApiKeyPlatinumTierParameter\n          PREMIUM_TIER_API_KEY: !Ref ApiKeyPremiumTierParameter\n          STANDARD_TIER_API_KEY: !Ref ApiKeyStandardTierParameter\n          BASIC_TIER_API_KEY: !Ref ApiKeyBasicTierParameter\n                 \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n      \n  GetTenantConfigFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.load_tenant_config\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n                  \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          PLATINUM_TIER_API_KEY: !Ref ApiKeyPlatinumTierParameter\n          PREMIUM_TIER_API_KEY: !Ref ApiKeyPremiumTierParameter\n          STANDARD_TIER_API_KEY: !Ref ApiKeyStandardTierParameter\n          BASIC_TIER_API_KEY: !Ref ApiKeyBasicTierParameter\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n  #Tenant Provisioning\n  ProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-provisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-provisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem      \n                  - dynamodb:DeleteItem                  \n                Resource:\n                  - !Ref TenantStackMappingTableArn\n              - Effect: Allow\n                Action:\n                  - codepipeline:StartPipelineExecution\n                Resource:\n                  - !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:serverless-saas-pipeline\n              - Effect: Allow\n                Action:\n                  - cloudformation:DeleteStack\n                Resource: \"*\"                        \n  ProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.provision_tenant\n      Runtime: python3.9\n      Role: !GetAtt ProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n        \n  DeProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-deprovisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-deprovisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            #Since this lambda is invoking cloudformation which is inturn removing AWS resources, we are giving overly permissive permissions to this lambda. \n            #You can limit this based upon your use case and AWS Resources you need to remove.\n            Statement: \n              - Effect: Allow\n                Action: \"*\"                  \n                Resource: \"*\"                     \n  DeProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: DeProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.deprovision_tenant\n      Runtime: python3.9\n      Role: !GetAtt DeProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n         \n  UpdateSettingsTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-settingstable-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-settingstable-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref ServerlessSaaSSettingsTableArn\n  UpdateSettingsTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateSettingsTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_settings_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateSettingsTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantStackMapTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-tenantstackmap-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-tenantstackmap-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref TenantStackMappingTableArn\n  UpdateTenantStackMapTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantStackMapTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_tenantstackmap_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantStackMapTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers        \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ProvisionTenantFunctionArn: \n    Value: !GetAtt ProvisionTenantFunction.Arn\n  DeProvisionTenantFunctionArn: \n    Value: !GetAtt DeProvisionTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantConfigFunctionArn:\n    Value: !GetAtt GetTenantConfigFunction.Arn  \n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  AuthorizerExecutionRoleArn:\n    Value: !GetAtt AuthorizerExecutionRole.Arn      \n  UpdateSettingsTableFunctionArn:\n    Value: !GetAtt UpdateSettingsTableFunction.Arn    \n  UpdateTenantStackMapTableFunctionArn:\n    Value: !GetAtt UpdateTenantStackMapTableFunction.Arn\n  "
  },
  {
    "path": "Lab6/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  ServerlessSaaSSettingsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: settingName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: settingName\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-Settings\n  TenantStackMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantStackMapping\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE      \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL\nOutputs:\n  ServerlessSaaSSettingsTableArn: \n    Value: !GetAtt ServerlessSaaSSettingsTable.Arn\n  ServerlessSaaSSettingsTableName: \n    Value: !Ref ServerlessSaaSSettingsTable\n  TenantStackMappingTableArn: \n    Value: !GetAtt TenantStackMappingTable.Arn\n  TenantStackMappingTableName: \n    Value: !Ref TenantStackMappingTable\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Lab6/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n\n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'       \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName    \n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName   \n    Condition: IsNotRunningInEventEngine\n"
  },
  {
    "path": "Lab6/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab6/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  ApiKeyOperationUsersParameter:\n    Type: String\n    Default: \"9a7743fa-3ae7-11eb-adc1-0242ac120002\"\n    Description: \"Enter default api key value to be used by api gateway for system admins\"\n  ApiKeyPlatinumTierParameter:\n    Type: String\n    Default: \"88b43c36-802e-11eb-af35-38f9d35b2c15\"\n    Description: \"Enter default api key value to be used by api gateway for platinum tier tenants\"\n  ApiKeyPremiumTierParameter:\n    Type: String\n    Default: \"6db2bdc2-6d96-11eb-a56f-38f9d33cfd0f\"\n    Description: \"Enter default api key value to be used by api gateway for premium tier tenants\"\n  ApiKeyStandardTierParameter:\n    Type: String\n    Default: \"b1c735d8-6d96-11eb-a28b-38f9d33cfd0f\"\n    Description: \"Enter default api key value to be used by api gateway for standard tier tenants\"\n  ApiKeyBasicTierParameter:\n    Type: String\n    Default: \"daae9784-6d96-11eb-a28b-38f9d33cfd0f\"\n    Description: \"Enter default api key value to be used by api gateway for basic tier tenants\"  \n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]\nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn\n        ApiKeyOperationUsersParameter: !Ref ApiKeyOperationUsersParameter\n        ApiKeyPlatinumTierParameter: !Ref ApiKeyPlatinumTierParameter\n        ApiKeyPremiumTierParameter: !Ref ApiKeyPremiumTierParameter\n        ApiKeyStandardTierParameter: !Ref ApiKeyStandardTierParameter\n        ApiKeyBasicTierParameter: !Ref ApiKeyBasicTierParameter\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn \n        ApiKeyOperationUsersParameter: !Ref ApiKeyOperationUsersParameter\n        ApiKeyPlatinumTierParameter: !Ref ApiKeyPlatinumTierParameter\n        ApiKeyPremiumTierParameter: !Ref ApiKeyPremiumTierParameter\n        ApiKeyStandardTierParameter: !Ref ApiKeyStandardTierParameter\n        ApiKeyBasicTierParameter: !Ref ApiKeyBasicTierParameter \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n  #setup custom resources\n  CustomResources:\n    Type: AWS::Serverless::Application\n    DependsOn: APIs    \n    Properties:\n      Location: nested_templates/custom_resources.yaml\n      Parameters:\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn  \n        ServerlessSaaSSettingsTableName: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableName\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        UpdateSettingsTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateSettingsTableFunctionArn\n        UpdateTenantStackMapTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantStackMapTableFunctionArn\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId      \n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolId:\n    Description: The user pool id of Admin Management userpool \n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolId\"  \n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolClientId\"\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  \n  AuthorizerExecutionRoleArn:\n    Description: The Lambda authorizer execution role\n    Value: !GetAtt LambdaFunctions.Outputs.AuthorizerExecutionRoleArn \n    Export:\n      Name: \"Serverless-SaaS-AuthorizerExecutionRoleArn\"   \n  UsagePlanBasicTier: \n    Description: The basic tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanBasicTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanBasicTier\"\n  UsagePlanStandardTier: \n    Description: The standard tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanStandardTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanStandardTier\"\n  UsagePlanPremiumTier: \n    Description: The premium tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanPremiumTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanPremiumTier\"\n  UsagePlanPlatinumTier: \n    Description: The premium tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanPlatinumTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanPlatinumTier\"\n  ApiKeyOperationUsers:\n    Description: The api key of system admins\n    Value: !Ref ApiKeyOperationUsersParameter\n    Export:\n      Name: \"Serverless-SaaS-ApiKeyOperationUsers\"    \n          "
  },
  {
    "path": "Lab6/server/tenant-buildspec.yml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nversion: 0.2\nphases:\n  install:    \n    runtime-versions:\n      python: 3.9\n    commands:\n      # Install packages or any pre-reqs in this phase.\n      # Upgrading SAM CLI to 1.33.0 version\n      - python -m pip install aws-sam-cli==1.33.0\n      - sam --version\n      # Installing project dependencies\n      - cd Lab6/server/ProductService\n      - python -m pip install -r requirements.txt \n      - cd ../OrderService\n      - python -m pip install -r requirements.txt \n      \n\n  pre_build:\n    commands:\n      # Run tests, lint scripts or any other pre-build checks.\n      - cd ..\n      - export PYTHONPATH=./ProductService/\n      # unit tests needs to be fixed. Commenting for now\n      #- python -m pytest tests/unit/ProductService-test_handler.py\n\n  build:\n    commands:\n      # Use Build phase to build your artifacts (compile, etc.)\n      - sam build -t tenant-template.yaml\n\n  post_build:\n    commands:\n      # Use Post-Build for notifications, git tags, upload artifacts to S3\n      - sam package --s3-bucket $PACKAGE_BUCKET --output-template-file packaged.yaml\n\nartifacts:\n  discard-paths: yes\n  files:\n    # List of local artifacts that will be passed down the pipeline\n    - Lab6/server/packaged.yaml"
  },
  {
    "path": "Lab6/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\ncached=\"true\"\nparallel=\"true\"\n"
  },
  {
    "path": "Lab6/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  TenantIdParameter:\n    Type: String\n    Default: pooled\n    Description: Tenant ID for the stack\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\n  LambdaReserveConcurrency:\n    Type: Number\n    Default: 20\n    Description: \"Reserve concurrency for lambda function. You can customize this on per tenant basis, if needed, by storing in the tenant table\"\nConditions:\n  IsPooledDeploy: !Equals [ !Ref TenantIdParameter, pooled] \n  IsSiloDeploy: !Not [!Equals [ !Ref TenantIdParameter, pooled]]    \nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: !Join ['-', [serverless-saas-dependencies, !Ref TenantIdParameter]]\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Product, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Order, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  ProductFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, product-function-policy]]\n      Roles: \n        - !Ref ProductFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt ProductTable.Arn\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, product-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  OrderFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, order-function-policy]]\n      Roles: \n        - !Ref OrderFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt OrderTable.Arn\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, order-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n        \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !ImportValue Serverless-SaaS-AuthorizerExecutionRoleArn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolClientId\n          OPERATION_USERS_API_KEY : !ImportValue Serverless-SaaS-ApiKeyOperationUsers\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Join ['-', [/aws/api-gateway/access-logs-serverless-saas-tenant-api-, !Ref TenantIdParameter]]\n      RetentionInDays: 30\n  ThrottlingLimitMetricFilter:\n    Type: AWS::Logs::MetricFilter\n    Properties:\n      LogGroupName: \n        Ref: \"ApiGatewayAccessLogs\"\n      FilterPattern: '{$.status = \"429\"}'\n      MetricTransformations:\n        - \n          MetricValue: \"1\"\n          MetricNamespace: \"Serverless-SaaS-Reference-Architecture\"\n          MetricName: !Join ['-', [\"ThrottlingLimitExceeded\", !Ref TenantIdParameter]]\n  ThrottlingLimitExceeded:\n    Type: AWS::CloudWatch::Alarm\n    Properties:\n      AlarmDescription: Throttling limit exceeded errors\n      ComparisonOperator: GreaterThanThreshold\n      EvaluationPeriods: 1\n      MetricName: !Join ['-', [\"ThrottlingLimitExceeded\", !Ref TenantIdParameter]]\n      Namespace: \"Serverless-SaaS-Reference-Architecture\"\n      Period: 60\n      Statistic: SampleCount\n      Threshold: 0 \n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join ['-', [!Ref TenantIdParameter, 'serverless-saas-tenant-api']]\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - api_key: []      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:      \n                - api_key: [] \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:    \n                - api_key: []     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n\n  UpdateUsagePlanLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    DependsOn: ApiGatewayTenantApi\n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, update-usage-plan-role]]\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n      Policies:\n        - PolicyName: !Join ['-', [!Ref TenantIdParameter, update-usage-plan-policy]]\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - kms:Decrypt\n                Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*\n              - Effect: Allow\n                Action:\n                  - logs:CreateLogGroup\n                  - logs:PutLogEvents\n                  - logs:CreateLogStream\n                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*\n              - Effect: Allow\n                Action:\n                  - logs:DescribeLogStreams\n                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*\n              - Effect: Allow\n                Action:\n                  - xray:PutTraceSegments\n                  - xray:PutTelemetryRecords\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - apigateway:PATCH\n                Resource: !Sub arn:aws:apigateway:${AWS::Region}::/usageplans/* \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-Settings\n  UpdateUsagePlanFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateUsagePlanLambdaExecutionRole\n    Properties:\n      CodeUri: custom_resources/\n      Handler: update_usage_plan.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateUsagePlanLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  AssociateUsagePlanWithTenantAPI:\n    Type: Custom::AssociateUsagePlanWithTenantAPI\n    DependsOn: UpdateUsagePlanFunction\n    Properties:\n      ServiceToken: !GetAtt UpdateUsagePlanFunction.Arn\n      ApiGatewayId: !Ref ApiGatewayTenantApi\n      SettingsTableName: ServerlessSaaS-Settings\n      IsPooledDeploy: !If [IsPooledDeploy, true, false]     \n      Stage: !Ref StageName\n      UsagePlanBasicTier: !ImportValue Serverless-SaaS-UsagePlanBasicTier\n      UsagePlanStandardTier: !ImportValue Serverless-SaaS-UsagePlanStandardTier\n      UsagePlanPremiumTier: !ImportValue Serverless-SaaS-UsagePlanPremiumTier\n      UsagePlanPlatinumTier: !ImportValue Serverless-SaaS-UsagePlanPlatinumTier    \n  UpdateTenantApiGatewayUrlLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    DependsOn: ApiGatewayTenantApi\n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exec-role]]\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exe-policy ]]\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-Settings \n              - Effect: Allow\n                Action:\n                  - dynamodb:UpdateItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-TenantDetails    \n  UpdateTenantApiGatewayUrlFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantApiGatewayUrlLambdaExecutionRole\n    Properties:\n      CodeUri: custom_resources/\n      Handler: update_tenant_apigatewayurl.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantApiGatewayUrlLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantApiGatewayUrl:\n    Type: Custom::UpdateTenantApiGatewayUrl\n    DependsOn: UpdateTenantApiGatewayUrlFunction\n    Properties:\n      ServiceToken: !GetAtt UpdateTenantApiGatewayUrlFunction.Arn\n      TenantDetailsTableName: ServerlessSaaS-TenantDetails\n      SettingsTableName: ServerlessSaaS-Settings  \n      TenantId: !Ref TenantIdParameter  \n      TenantApiGatewayUrl: !Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/prod/\"    \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Lab7/.aws-sam/build/GetDynamoDBUsageAndCostByTenant/requirements.txt",
    "content": ""
  },
  {
    "path": "Lab7/.aws-sam/build/GetDynamoDBUsageAndCostByTenant/tenant_usage_and_cost.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport boto3\nimport time\nimport os\nfrom datetime import datetime, timedelta\nfrom botocore.exceptions import ClientError\nfrom decimal import *\n\ncloudformation = boto3.client('cloudformation')\nlogs = boto3.client('logs')\nathena = boto3.client('athena')\ndynamodb = boto3.resource('dynamodb')\nattribution_table = dynamodb.Table(\"TenantCostAndUsageAttribution\")\n\nATHENA_S3_OUTPUT = os.getenv(\"ATHENA_S3_OUTPUT\")\nRETRY_COUNT = 100\n\n#This function needs to be scheduled on daily basis\ndef calculate_daily_dynamodb_attribution_by_tenant(event, context):\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_dynamodb_cost = __get_total_service_cost('AmazonDynamoDB', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields tenant_id as TenantId, service as Service, \\\n     ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by TenantId, dateceil(@timestamp, 1d) as timestamp'\n\n    print( log_group_names)\n    \n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day)    \n    #optionally save this data in a table\n    \n    total_usage_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day)  \n    \n    total_RCU = 0 \n    total_WCU = 0 \n    for result in total_usage_by_day['results'][0]:\n        if 'ReadCapacityUnits' in result['field']:\n            total_RCU = Decimal(result['value'])\n        if 'WriteCapacityUnits' in result['field']:\n            total_WCU = Decimal(result['value'])\n    \n    print (total_RCU)\n    print (total_WCU)\n    \n    if (total_RCU + total_WCU > 0):\n        total_RCU_By_Tenant = 0\n        total_WCU_By_Tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'ReadCapacityUnits' in field['field']:\n                    total_RCU_By_Tenant = Decimal(field['value'])\n                if 'WriteCapacityUnits' in field['field']:\n                    total_WCU_By_Tenant = Decimal(field['value'])\n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (((total_RCU_By_Tenant * 5) + total_WCU_By_Tenant) / ((total_RCU * 5) + total_WCU)) \n            tenant_dynamodb_cost = tenant_attribution_percentage * total_dynamodb_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"DynamoDB\",\n                            \"TenantId\": tenant_id, \n                            \"TotalRCU\": total_RCU, \n                            \"TenantTotalRCU\": total_RCU_By_Tenant, \n                            \"TotalWCU\": total_WCU,\n                            \"TenantTotalWCU\": total_WCU_By_Tenant, \n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_dynamodb_cost,\n                            \"TotalServiceCost\": total_dynamodb_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n                \n            tenant_id = 'unknown'\n            total_RCU_By_Tenant = 0.0\n            total_WCU_By_Tenant = 0.0\n        \n    \n    \n#Below function considers number of invocation as the metrics to calculate usage and cost. \n#You can go granluar by recording duration of each metrics and use that to get more granular\n#Since our functions are basic CRUD this might work as a ball park cost estimate\ndef calculate_daily_lambda_attribution_by_tenant(event, context):\n    \n    #Get total dynamodb cost for the given duration\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_lambda_cost = __get_total_service_cost('AWSLambda', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query='fields @timestamp, @message \\\n        | filter @message like /Request completed/ \\\n        | fields tenant_id as TenantId , CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by TenantId, dateceil(@timestamp, 1d) as timestamp'\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day) \n\n    total_usage_by_day_query = 'filter @message like /Request completed/ \\\n        | fields CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day) \n    \n    total_invocations = 1 #to avoid divide by zero\n    for result in total_usage_by_day['results'][0]:\n        if 'LambdaInvocations' in result['field']:\n            total_invocations = Decimal(result['value'])\n        \n    \n    print (total_invocations)\n    \n    if (total_invocations>0):\n        total_invocations_by_tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'LambdaInvocations' in field['field']:\n                    total_invocations_by_tenant = Decimal(field['value'])\n                \n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (total_invocations_by_tenant / total_invocations) \n            tenant_lambda_cost = tenant_attribution_percentage * total_lambda_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"AWSLambda\",\n                            \"TenantId\": tenant_id, \n                            \"TotalInvocations\": total_invocations, \n                            \"TenantTotalInvocations\": total_invocations_by_tenant,\n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_lambda_cost,\n                            \"TotalServiceCost\": total_lambda_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n            \n            tenant_id = 'unknown'\n            tenant_total_RCU = 0.0\n            tenant_total_WCU = 0.0\n\ndef __get_total_service_cost(servicename, start_date_time, end_date_time):\n\n    # We need to add more filters for day, month, year, resource ids etc. Below query is because we are just using a sample cur file\n    #Ignoting startTime and endTime filter for now since we have a static/sample cur file\n    \n    query = \"SELECT sum(line_item_blended_cost) AS cost FROM costexplorerdb.curoutput WHERE line_item_product_code='{0}'\".format(servicename) \n\n    # Execution\n    response = athena.start_query_execution(\n        QueryString=query,\n        QueryExecutionContext={\n            'Database': 'costexplorerdb'\n        },\n        ResultConfiguration={\n            'OutputLocation': \"s3://\" + ATHENA_S3_OUTPUT,\n        }\n    )\n\n    # get query execution id\n    query_execution_id = response['QueryExecutionId']\n    print(query_execution_id)\n\n    # get execution status\n    for i in range(1, 1 + RETRY_COUNT):\n\n        # get query execution\n        query_status = athena.get_query_execution(QueryExecutionId=query_execution_id)\n        print (query_status)\n        query_execution_status = query_status['QueryExecution']['Status']['State']\n\n        if query_execution_status == 'SUCCEEDED':\n            print(\"STATUS:\" + query_execution_status)\n            break\n\n        if query_execution_status == 'FAILED':\n            raise Exception(\"STATUS:\" + query_execution_status)\n\n        else:\n            print(\"STATUS:\" + query_execution_status)\n            time.sleep(i)\n    else:\n        athena.stop_query_execution(QueryExecutionId=query_execution_id)\n        raise Exception('TIME OVER')\n\n    # get query results\n    result = athena.get_query_results(QueryExecutionId=query_execution_id)\n    \n    print (result)\n    \n    \n    total_dynamo_db_cost = result['ResultSet']['Rows'][1]['Data'][0]['VarCharValue']\n    print(total_dynamo_db_cost)\n    \n    return Decimal(total_dynamo_db_cost)\n    \ndef __query_cloudwatch_logs(logs, log_group_names, query_string, start_time, end_time):\n    query = logs.start_query(logGroupNames=log_group_names,\n    startTime=start_time,\n    endTime=end_time,\n    queryString=query_string)\n\n    query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    while query_results['status']=='Running' or query_results['status']=='Scheduled':\n        time.sleep(5)\n        query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    return query_results\n\ndef __is_log_group_exists(logs_client, log_group_name):\n    \n    logs_paginator = logs_client.get_paginator('describe_log_groups')\n    response_iterator = logs_paginator.paginate(logGroupNamePrefix=log_group_name)\n    for log_groups_list in response_iterator:\n        if not log_groups_list[\"logGroups\"]:\n            return False\n        else:\n            return True       \n\ndef __add_log_group_name(logs_client, log_group_name, log_group_names_list):\n\n    if __is_log_group_exists(logs_client, log_group_name):\n        log_group_names_list.append(log_group_name)\n\n\ndef __get_list_of_log_group_names():\n    log_group_names = []\n    log_group_prefix = '/aws/lambda/'\n    cloudformation_paginator = cloudformation.get_paginator('list_stack_resources')\n    response_iterator = cloudformation_paginator.paginate(StackName='stack-pooled')\n    for stack_resources in response_iterator:\n        for resource in stack_resources['StackResourceSummaries']:\n            if (resource[\"LogicalResourceId\"] == \"CreateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue    \n            if (resource[\"LogicalResourceId\"] == \"UpdateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetProductsFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue         \n            if (resource[\"LogicalResourceId\"] == \"CreateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"UpdateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetOrdersFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue \n\n    return log_group_names                     \n\n"
  },
  {
    "path": "Lab7/.aws-sam/build/GetLambdaUsageAndCostByTenant/requirements.txt",
    "content": ""
  },
  {
    "path": "Lab7/.aws-sam/build/GetLambdaUsageAndCostByTenant/tenant_usage_and_cost.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport boto3\nimport time\nimport os\nfrom datetime import datetime, timedelta\nfrom botocore.exceptions import ClientError\nfrom decimal import *\n\ncloudformation = boto3.client('cloudformation')\nlogs = boto3.client('logs')\nathena = boto3.client('athena')\ndynamodb = boto3.resource('dynamodb')\nattribution_table = dynamodb.Table(\"TenantCostAndUsageAttribution\")\n\nATHENA_S3_OUTPUT = os.getenv(\"ATHENA_S3_OUTPUT\")\nRETRY_COUNT = 100\n\n#This function needs to be scheduled on daily basis\ndef calculate_daily_dynamodb_attribution_by_tenant(event, context):\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_dynamodb_cost = __get_total_service_cost('AmazonDynamoDB', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields tenant_id as TenantId, service as Service, \\\n     ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by TenantId, dateceil(@timestamp, 1d) as timestamp'\n\n    print( log_group_names)\n    \n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day)    \n    #optionally save this data in a table\n    \n    total_usage_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day)  \n    \n    total_RCU = 0 \n    total_WCU = 0 \n    for result in total_usage_by_day['results'][0]:\n        if 'ReadCapacityUnits' in result['field']:\n            total_RCU = Decimal(result['value'])\n        if 'WriteCapacityUnits' in result['field']:\n            total_WCU = Decimal(result['value'])\n    \n    print (total_RCU)\n    print (total_WCU)\n    \n    if (total_RCU + total_WCU > 0):\n        total_RCU_By_Tenant = 0\n        total_WCU_By_Tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'ReadCapacityUnits' in field['field']:\n                    total_RCU_By_Tenant = Decimal(field['value'])\n                if 'WriteCapacityUnits' in field['field']:\n                    total_WCU_By_Tenant = Decimal(field['value'])\n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (((total_RCU_By_Tenant * 5) + total_WCU_By_Tenant) / ((total_RCU * 5) + total_WCU)) \n            tenant_dynamodb_cost = tenant_attribution_percentage * total_dynamodb_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"DynamoDB\",\n                            \"TenantId\": tenant_id, \n                            \"TotalRCU\": total_RCU, \n                            \"TenantTotalRCU\": total_RCU_By_Tenant, \n                            \"TotalWCU\": total_WCU,\n                            \"TenantTotalWCU\": total_WCU_By_Tenant, \n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_dynamodb_cost,\n                            \"TotalServiceCost\": total_dynamodb_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n                \n            tenant_id = 'unknown'\n            total_RCU_By_Tenant = 0.0\n            total_WCU_By_Tenant = 0.0\n        \n    \n    \n#Below function considers number of invocation as the metrics to calculate usage and cost. \n#You can go granluar by recording duration of each metrics and use that to get more granular\n#Since our functions are basic CRUD this might work as a ball park cost estimate\ndef calculate_daily_lambda_attribution_by_tenant(event, context):\n    \n    #Get total dynamodb cost for the given duration\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_lambda_cost = __get_total_service_cost('AWSLambda', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query='fields @timestamp, @message \\\n        | filter @message like /Request completed/ \\\n        | fields tenant_id as TenantId , CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by TenantId, dateceil(@timestamp, 1d) as timestamp'\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day) \n\n    total_usage_by_day_query = 'filter @message like /Request completed/ \\\n        | fields CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day) \n    \n    total_invocations = 1 #to avoid divide by zero\n    for result in total_usage_by_day['results'][0]:\n        if 'LambdaInvocations' in result['field']:\n            total_invocations = Decimal(result['value'])\n        \n    \n    print (total_invocations)\n    \n    if (total_invocations>0):\n        total_invocations_by_tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'LambdaInvocations' in field['field']:\n                    total_invocations_by_tenant = Decimal(field['value'])\n                \n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (total_invocations_by_tenant / total_invocations) \n            tenant_lambda_cost = tenant_attribution_percentage * total_lambda_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"AWSLambda\",\n                            \"TenantId\": tenant_id, \n                            \"TotalInvocations\": total_invocations, \n                            \"TenantTotalInvocations\": total_invocations_by_tenant,\n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_lambda_cost,\n                            \"TotalServiceCost\": total_lambda_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n            \n            tenant_id = 'unknown'\n            tenant_total_RCU = 0.0\n            tenant_total_WCU = 0.0\n\ndef __get_total_service_cost(servicename, start_date_time, end_date_time):\n\n    # We need to add more filters for day, month, year, resource ids etc. Below query is because we are just using a sample cur file\n    #Ignoting startTime and endTime filter for now since we have a static/sample cur file\n    \n    query = \"SELECT sum(line_item_blended_cost) AS cost FROM costexplorerdb.curoutput WHERE line_item_product_code='{0}'\".format(servicename) \n\n    # Execution\n    response = athena.start_query_execution(\n        QueryString=query,\n        QueryExecutionContext={\n            'Database': 'costexplorerdb'\n        },\n        ResultConfiguration={\n            'OutputLocation': \"s3://\" + ATHENA_S3_OUTPUT,\n        }\n    )\n\n    # get query execution id\n    query_execution_id = response['QueryExecutionId']\n    print(query_execution_id)\n\n    # get execution status\n    for i in range(1, 1 + RETRY_COUNT):\n\n        # get query execution\n        query_status = athena.get_query_execution(QueryExecutionId=query_execution_id)\n        print (query_status)\n        query_execution_status = query_status['QueryExecution']['Status']['State']\n\n        if query_execution_status == 'SUCCEEDED':\n            print(\"STATUS:\" + query_execution_status)\n            break\n\n        if query_execution_status == 'FAILED':\n            raise Exception(\"STATUS:\" + query_execution_status)\n\n        else:\n            print(\"STATUS:\" + query_execution_status)\n            time.sleep(i)\n    else:\n        athena.stop_query_execution(QueryExecutionId=query_execution_id)\n        raise Exception('TIME OVER')\n\n    # get query results\n    result = athena.get_query_results(QueryExecutionId=query_execution_id)\n    \n    print (result)\n    \n    \n    total_dynamo_db_cost = result['ResultSet']['Rows'][1]['Data'][0]['VarCharValue']\n    print(total_dynamo_db_cost)\n    \n    return Decimal(total_dynamo_db_cost)\n    \ndef __query_cloudwatch_logs(logs, log_group_names, query_string, start_time, end_time):\n    query = logs.start_query(logGroupNames=log_group_names,\n    startTime=start_time,\n    endTime=end_time,\n    queryString=query_string)\n\n    query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    while query_results['status']=='Running' or query_results['status']=='Scheduled':\n        time.sleep(5)\n        query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    return query_results\n\ndef __is_log_group_exists(logs_client, log_group_name):\n    \n    logs_paginator = logs_client.get_paginator('describe_log_groups')\n    response_iterator = logs_paginator.paginate(logGroupNamePrefix=log_group_name)\n    for log_groups_list in response_iterator:\n        if not log_groups_list[\"logGroups\"]:\n            return False\n        else:\n            return True       \n\ndef __add_log_group_name(logs_client, log_group_name, log_group_names_list):\n\n    if __is_log_group_exists(logs_client, log_group_name):\n        log_group_names_list.append(log_group_name)\n\n\ndef __get_list_of_log_group_names():\n    log_group_names = []\n    log_group_prefix = '/aws/lambda/'\n    cloudformation_paginator = cloudformation.get_paginator('list_stack_resources')\n    response_iterator = cloudformation_paginator.paginate(StackName='stack-pooled')\n    for stack_resources in response_iterator:\n        for resource in stack_resources['StackResourceSummaries']:\n            if (resource[\"LogicalResourceId\"] == \"CreateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue    \n            if (resource[\"LogicalResourceId\"] == \"UpdateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetProductsFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue         \n            if (resource[\"LogicalResourceId\"] == \"CreateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"UpdateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetOrdersFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue \n\n    return log_group_names                     \n\n"
  },
  {
    "path": "Lab7/.aws-sam/build/template.yaml",
    "content": "AWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: 'Serverless SaaS - Cost by tenant\n\n  '\nGlobals:\n  Function:\n    Timeout: 29\nResources:\n  CURBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy: Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n        - ServerSideEncryptionByDefault:\n            SSEAlgorithm: AES256\n      PublicAccessBlockConfiguration:\n        BlockPublicAcls: true\n        BlockPublicPolicy: true\n        IgnorePublicAcls: true\n        RestrictPublicBuckets: true\n  AWSCURDatabase:\n    Type: AWS::Glue::Database\n    Properties:\n      DatabaseInput:\n        Name:\n          Fn::Sub: costexplorerdb\n      CatalogId:\n        Ref: AWS::AccountId\n  AWSCURCrawlerComponentFunction:\n    Type: AWS::IAM::Role\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - glue.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      Path: /\n      ManagedPolicyArns:\n      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole\n      Policies:\n      - PolicyName: AWSCURCrawlerComponentFunction\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - logs:CreateLogGroup\n            - logs:CreateLogStream\n            - logs:PutLogEvents\n            Resource:\n              Fn::Sub: arn:${AWS::Partition}:logs:*:*:*\n          - Effect: Allow\n            Action:\n            - glue:UpdateDatabase\n            - glue:UpdatePartition\n            - glue:CreateTable\n            - glue:UpdateTable\n            - glue:ImportCatalogToGlue\n            Resource: '*'\n          - Effect: Allow\n            Action:\n            - s3:GetObject\n            - s3:PutObject\n            Resource:\n              Fn::Sub: ${CURBucket.Arn}*\n      - PolicyName: AWSCURKMSDecryption\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - kms:Decrypt\n            Resource: '*'\n  AWSCURCrawlerLambdaExecutor:\n    Type: AWS::IAM::Role\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - lambda.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      Path: /\n      Policies:\n      - PolicyName: AWSCURCrawlerLambdaExecutor\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - logs:CreateLogGroup\n            - logs:CreateLogStream\n            - logs:PutLogEvents\n            Resource:\n              Fn::Sub: arn:${AWS::Partition}:logs:*:*:*\n          - Effect: Allow\n            Action:\n            - glue:StartCrawler\n            Resource: '*'\n  AWSCURCrawler:\n    Type: AWS::Glue::Crawler\n    DependsOn:\n    - AWSCURDatabase\n    - AWSCURCrawlerComponentFunction\n    Properties:\n      Name: AWSCURCrawler-Multi-tenant\n      Description: A recurring crawler that keeps your CUR table in Athena up-to-date.\n      Role:\n        Fn::GetAtt:\n        - AWSCURCrawlerComponentFunction\n        - Arn\n      DatabaseName:\n        Ref: AWSCURDatabase\n      Targets:\n        S3Targets:\n        - Path:\n            Fn::Sub: s3://${CURBucket}/curoutput\n          Exclusions:\n          - '**.json'\n          - '**.yml'\n          - '**.sql'\n          - '**.csv'\n          - '**.gz'\n          - '**.zip'\n      SchemaChangePolicy:\n        UpdateBehavior: UPDATE_IN_DATABASE\n        DeleteBehavior: DELETE_FROM_DATABASE\n  AWSCURInitializer:\n    Type: AWS::Lambda::Function\n    DependsOn: AWSCURCrawler\n    Properties:\n      Code:\n        ZipFile: \"const AWS = require('aws-sdk'); const response = require('./cfn-response');\\\n          \\ exports.handler = function(event, context, callback) {\\n  if (event.RequestType\\\n          \\ === 'Delete') {\\n    response.send(event, context, response.SUCCESS);\\n\\\n          \\  } else {\\n    const glue = new AWS.Glue();\\n    glue.startCrawler({ Name:\\\n          \\ 'AWSCURCrawler-Multi-tenant' }, function(err, data) {\\n      if (err)\\\n          \\ {\\n        const responseData = JSON.parse(this.httpResponse.body);\\n\\\n          \\        if (responseData['__type'] == 'CrawlerRunningException') {\\n  \\\n          \\        callback(null, responseData.Message);\\n        } else {\\n     \\\n          \\     const responseString = JSON.stringify(responseData);\\n          if\\\n          \\ (event.ResponseURL) {\\n            response.send(event, context, response.FAILED,{\\\n          \\ msg: responseString });\\n          } else {\\n            callback(responseString);\\n\\\n          \\          }\\n        }\\n      }\\n      else {\\n        if (event.ResponseURL)\\\n          \\ {\\n          response.send(event, context, response.SUCCESS);\\n      \\\n          \\  } else {\\n          callback(null, response.SUCCESS);\\n        }\\n  \\\n          \\    }\\n    });\\n  }\\n};\\n\"\n      Handler: index.handler\n      Timeout: 30\n      Runtime: nodejs16.x\n      ReservedConcurrentExecutions: 1\n      Role:\n        Fn::GetAtt:\n        - AWSCURCrawlerLambdaExecutor\n        - Arn\n  TenantCostandUsageAttributionTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n      - AttributeName: Date\n        AttributeType: N\n      - AttributeName: ServiceName\n        AttributeType: S\n      KeySchema:\n      - AttributeName: Date\n        KeyType: HASH\n      - AttributeName: ServiceName\n        KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST\n      TableName: TenantCostAndUsageAttribution\n  QueryLogInsightsExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: product-function-execution-role-lab1\n      Path: /\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - lambda.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      ManagedPolicyArns:\n      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n      Policies:\n      - PolicyName: query-log-insight-lab7\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - logs:GetQueryResults\n            - logs:StartQuery\n            - logs:StopQuery\n            - logs:FilterLogEvents\n            - logs:DescribeLogGroups\n            - cloudformation:ListStackResources\n            Resource:\n            - '*'\n          - Effect: Allow\n            Action:\n            - s3:*\n            Resource:\n            - Fn::Sub: arn:aws:s3:::${CURBucket}*\n          - Effect: Allow\n            Action:\n            - dynamodb:*\n            Resource:\n            - Fn::GetAtt:\n              - TenantCostandUsageAttributionTable\n              - Arn\n          - Effect: Allow\n            Action:\n            - Athena:*\n            Resource:\n            - '*'\n          - Effect: Allow\n            Action:\n            - glue:*\n            Resource:\n            - '*'\n  GetDynamoDBUsageAndCostByTenant:\n    Type: AWS::Serverless::Function\n    DependsOn: QueryLogInsightsExecutionRole\n    Properties:\n      CodeUri: GetDynamoDBUsageAndCostByTenant\n      Handler: tenant_usage_and_cost.calculate_daily_dynamodb_attribution_by_tenant\n      Runtime: python3.9\n      Role:\n        Fn::GetAtt:\n        - QueryLogInsightsExecutionRole\n        - Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT:\n            Ref: CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateDynamoUsageAndCostByTenant\n            Schedule: rate(5 minutes)\n  GetLambdaUsageAndCostByTenant:\n    Type: AWS::Serverless::Function\n    DependsOn: QueryLogInsightsExecutionRole\n    Properties:\n      CodeUri: GetLambdaUsageAndCostByTenant\n      Handler: tenant_usage_and_cost.calculate_daily_lambda_attribution_by_tenant\n      Runtime: python3.9\n      Role:\n        Fn::GetAtt:\n        - QueryLogInsightsExecutionRole\n        - Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT:\n            Ref: CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateLambdaUsageAndCostByTenant\n            Schedule: rate(5 minutes)\nOutputs:\n  CURBucketname:\n    Description: The name of S3 bucket name\n    Value:\n      Ref: CURBucket\n    Export:\n      Name: CURBucketname\n  AWSCURInitializerFunctionName:\n    Description: Function name of CUR initializer\n    Value:\n      Ref: AWSCURInitializer\n    Export:\n      Name: AWSCURInitializerFunctionName\n"
  },
  {
    "path": "Lab7/.aws-sam/build.toml",
    "content": "# This file is auto generated by SAM CLI build command\n\n[function_build_definitions]\n[function_build_definitions.8d6cee39-9fc9-4073-9b65-22bf29298af4]\ncodeuri = \"/Users/shaanubh/Documents/code/serverless-saas-workshop/code/aws-serverless-saas-workshop/Lab7/TenantUsageAndCost\"\nruntime = \"python3.8\"\narchitecture = \"x86_64\"\nmanifest_hash = \"\"\npackagetype = \"Zip\"\nfunctions = [\"GetDynamoDBUsageAndCostByTenant\", \"GetLambdaUsageAndCostByTenant\"]\n\n[layer_build_definitions]\n"
  },
  {
    "path": "Lab7/TenantUsageAndCost/requirements.txt",
    "content": ""
  },
  {
    "path": "Lab7/TenantUsageAndCost/tenant_usage_and_cost.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport boto3\nimport time\nimport os\nfrom datetime import datetime, timedelta\nfrom botocore.exceptions import ClientError\nfrom decimal import *\n\ncloudformation = boto3.client('cloudformation')\nlogs = boto3.client('logs')\nathena = boto3.client('athena')\ndynamodb = boto3.resource('dynamodb')\nattribution_table = dynamodb.Table(\"TenantCostAndUsageAttribution\")\n\nATHENA_S3_OUTPUT = os.getenv(\"ATHENA_S3_OUTPUT\")\nRETRY_COUNT = 100\n\n#This function needs to be scheduled on daily basis\ndef calculate_daily_dynamodb_attribution_by_tenant(event, context):\n    start_date_time = __get_start_date_time() #current day epoch\n    end_date_time =  __get_end_date_time() #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    #TODO: Get total cost of DynamoDB for the current date\n    total_dynamodb_cost = 0\n\n    log_group_names = __get_list_of_log_group_names()\n    print( log_group_names)\n        \n    #TODO: Write the query to get the DynamoDB WCU and RCUs consumption grouped by TenantId\n    usage_by_tenant_by_day_query = 'query placeholder'\n    \n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n    print(usage_by_tenant_by_day)        \n    \n    #TODO: Write the query to get the Total DynamoDB WCU and RCUs consumption across all tenants\n    total_usage_by_day_query = 'query placeholder'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs, log_group_names, total_usage_by_day_query, start_date_time, end_date_time)\n    print(total_usage_by_day)  \n    \n    total_RCU = Decimal('0.0') \n    total_WCU = Decimal('0.0') \n    for result in total_usage_by_day['results'][0]:\n        if 'ReadCapacityUnits' in result['field']:\n            total_RCU = Decimal(result['value'])\n        if 'WriteCapacityUnits' in result['field']:\n            total_WCU = Decimal(result['value'])\n    \n    print (total_RCU)\n    print (total_WCU)\n    \n    if (total_RCU + total_WCU > 0):\n        total_RCU_By_Tenant = Decimal('0.0')\n        total_WCU_By_Tenant = Decimal('0.0')\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'ReadCapacityUnits' in field['field']:\n                    total_RCU_By_Tenant = Decimal(field['value'])\n                if 'WriteCapacityUnits' in field['field']:\n                    total_WCU_By_Tenant = Decimal(field['value'])\n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage_numerator= Decimal(str(total_RCU_By_Tenant * Decimal('5.0'))) + Decimal(str(total_WCU_By_Tenant)) \n            tenant_attribution_percentage_denominator= Decimal(str(total_RCU *  Decimal('5.0')))  + Decimal(str(total_WCU))\n            tenant_attribution_percentage = tenant_attribution_percentage_numerator/tenant_attribution_percentage_denominator        \n            tenant_dynamodb_cost = tenant_attribution_percentage * total_dynamodb_cost\n            \n            try:\n                #TODO: Save the tenant attribution data inside a dynamodb table\n                pass\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n                \n            tenant_id = 'unknown'\n            total_RCU_By_Tenant = 0.0\n            total_WCU_By_Tenant = 0.0\n        \n    \n    \n#Below function considers number of invocation as the metrics to calculate usage and cost. \n#You can go granluar by recording duration of each metrics and use that to get more granular\n#Since our functions are basic CRUD this might work as a ball park cost estimate\ndef calculate_daily_lambda_attribution_by_tenant(event, context):\n    \n    #Get total dynamodb cost for the given duration\n    start_date_time = __get_start_date_time() #current day epoch\n    end_date_time =  __get_end_date_time() #next day epoch\n\n    #Get total dynamodb cost for the given duration\n    total_lambda_cost = __get_total_service_cost('AWSLambda', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    #TODO: Write the below query to get the total lambda invocations grouped by tenants\n    usage_by_tenant_by_day_query='query placeholder'\n\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n    print(usage_by_tenant_by_day) \n\n    #TODO: Write the below query to get the total lambda invocations across all tenants\n    total_usage_by_day_query = 'query placeholder'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, total_usage_by_day_query, start_date_time, end_date_time)\n    print(total_usage_by_day) \n    \n    total_invocations = 1 #to avoid divide by zero\n    for result in total_usage_by_day['results'][0]:\n        if 'LambdaInvocations' in result['field']:\n            total_invocations = Decimal(result['value'])\n        \n    print (total_invocations)\n    \n    if (total_invocations>0):\n        total_invocations_by_tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'LambdaInvocations' in field['field']:\n                    total_invocations_by_tenant = Decimal(field['value'])\n                \n            tenant_attribution_percentage= (total_invocations_by_tenant / total_invocations) \n            tenant_lambda_cost = tenant_attribution_percentage * total_lambda_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"TenantId#ServiceName\": tenant_id+\"#\"+\"AWSLambda\",\n                            \"TenantId\": tenant_id, \n                            \"TotalInvocations\": total_invocations, \n                            \"TenantTotalInvocations\": total_invocations_by_tenant,\n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_lambda_cost,\n                            \"TotalServiceCost\": total_lambda_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n            \n            tenant_id = 'unknown'\n            total_invocations_by_tenant = 0\n            \n\ndef __get_total_service_cost(servicename, start_date_time, end_date_time):\n\n    # We need to add more filters for day, month, year, resource ids etc. Below query is because we are just using a sample cur file\n    #Ignoting startTime and endTime filter for now since we have a static/sample cur file\n    \n    query = \"SELECT sum(line_item_blended_cost) AS cost FROM costexplorerdb.curoutput WHERE line_item_product_code='{0}'\".format(servicename) \n\n    # Execution\n    response = athena.start_query_execution(\n        QueryString=query,\n        QueryExecutionContext={\n            'Database': 'costexplorerdb'\n        },\n        ResultConfiguration={\n            'OutputLocation': \"s3://\" + ATHENA_S3_OUTPUT,\n        }\n    )\n\n    # get query execution id\n    query_execution_id = response['QueryExecutionId']\n    print(query_execution_id)\n\n    # get execution status\n    for i in range(1, 1 + RETRY_COUNT):\n\n        # get query execution\n        query_status = athena.get_query_execution(QueryExecutionId=query_execution_id)\n        print (query_status)\n        query_execution_status = query_status['QueryExecution']['Status']['State']\n\n        if query_execution_status == 'SUCCEEDED':\n            print(\"STATUS:\" + query_execution_status)\n            break\n\n        if query_execution_status == 'FAILED':\n            raise Exception(\"STATUS:\" + query_execution_status)\n\n        else:\n            print(\"STATUS:\" + query_execution_status)\n            time.sleep(i)\n    else:\n        athena.stop_query_execution(QueryExecutionId=query_execution_id)\n        raise Exception('TIME OVER')\n\n    # get query results\n    result = athena.get_query_results(QueryExecutionId=query_execution_id)\n    \n    print (result)\n    \n    \n    total_dynamo_db_cost = result['ResultSet']['Rows'][1]['Data'][0]['VarCharValue']\n    print(total_dynamo_db_cost)\n    \n    return Decimal(total_dynamo_db_cost)\n    \ndef __query_cloudwatch_logs(logs, log_group_names, query_string, start_time, end_time):\n    query = logs.start_query(logGroupNames=log_group_names,\n    startTime=start_time,\n    endTime=end_time,\n    queryString=query_string)\n\n    query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    while query_results['status']=='Running' or query_results['status']=='Scheduled':\n        time.sleep(5)\n        query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    return query_results\n\ndef __is_log_group_exists(logs_client, log_group_name):\n    \n    logs_paginator = logs_client.get_paginator('describe_log_groups')\n    response_iterator = logs_paginator.paginate(logGroupNamePrefix=log_group_name)\n    for log_groups_list in response_iterator:\n        if not log_groups_list[\"logGroups\"]:\n            return False\n        else:\n            return True       \n\ndef __add_log_group_name(logs_client, log_group_name, log_group_names_list):\n\n    if __is_log_group_exists(logs_client, log_group_name):\n        log_group_names_list.append(log_group_name)\n\n\ndef __get_list_of_log_group_names():\n    log_group_names = []\n    log_group_prefix = '/aws/lambda/'\n    cloudformation_paginator = cloudformation.get_paginator('list_stack_resources')\n    response_iterator = cloudformation_paginator.paginate(StackName='stack-pooled')\n    for stack_resources in response_iterator:\n        for resource in stack_resources['StackResourceSummaries']:\n            if (resource[\"LogicalResourceId\"] == \"CreateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue    \n            if (resource[\"LogicalResourceId\"] == \"UpdateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetProductsFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue         \n            if (resource[\"LogicalResourceId\"] == \"CreateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"UpdateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetOrdersFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue \n\n    return log_group_names          \n\n                  \ndef __get_start_date_time():\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    return start_date_time\n\ndef __get_end_date_time():\n    time_zone = datetime.now().astimezone().tzinfo    \n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    return end_date_time\n"
  },
  {
    "path": "Lab7/deployment.sh",
    "content": "REGION=$(aws configure get region)\nsam build -t template.yaml --use-container\nsam deploy --config-file samconfig.toml --region=$REGION\n  \n\nCUR_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='CURBucketname'].Value\" --output text)\nAWSCURInitializerFunctionName=$(aws cloudformation list-exports --query \"Exports[?Name=='AWSCURInitializerFunctionName'].Value\" --output text)\n\naws s3 cp SampleCUR/ s3://$CUR_BUCKET/curoutput/year=2022/month=10/ --recursive\n\naws lambda invoke --function-name $AWSCURInitializerFunctionName lambdaoutput.json"
  },
  {
    "path": "Lab7/lambdaoutput.json",
    "content": "\"Crawler with name AWSCURCrawler-Multi-tenant has already started\""
  },
  {
    "path": "Lab7/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas-cost-per-tenant-lab7\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-1p6m7gm2vwaaz\"\ns3_prefix = \"serverless-saas-lab7\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Lab7/template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS - Cost by tenant\n\nGlobals:\n  Function:\n    Timeout: 29\n\nResources: \n  CURBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n        \n  AWSCURDatabase:\n    Type: 'AWS::Glue::Database'\n    Properties:\n      DatabaseInput:\n        Name: !Sub 'costexplorerdb'\n      CatalogId: !Ref AWS::AccountId\n\n         \n\n  AWSCURCrawlerComponentFunction:\n    Type: 'AWS::IAM::Role'\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - glue.amazonaws.com\n            Action:\n              - 'sts:AssumeRole'\n      Path: /\n      ManagedPolicyArns:\n        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole'\n      Policies:\n        - PolicyName: AWSCURCrawlerComponentFunction\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - 'logs:CreateLogGroup'\n                  - 'logs:CreateLogStream'\n                  - 'logs:PutLogEvents'\n                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'\n              - Effect: Allow\n                Action:\n                  - 'glue:UpdateDatabase'\n                  - 'glue:UpdatePartition'\n                  - 'glue:CreateTable'\n                  - 'glue:UpdateTable'\n                  - 'glue:ImportCatalogToGlue'\n                Resource: '*'\n              - Effect: Allow\n                Action:\n                  - 's3:GetObject'\n                  - 's3:PutObject'\n                Resource: !Sub '${CURBucket.Arn}*'\n        - PolicyName: AWSCURKMSDecryption\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - 'kms:Decrypt'\n                Resource: '*'\n       \n\n  AWSCURCrawlerLambdaExecutor:\n    Type: 'AWS::IAM::Role'\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - 'sts:AssumeRole'\n      Path: /\n      Policies:\n        - PolicyName: AWSCURCrawlerLambdaExecutor\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - 'logs:CreateLogGroup'\n                  - 'logs:CreateLogStream'\n                  - 'logs:PutLogEvents'\n                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'\n              - Effect: Allow\n                Action:\n                  - 'glue:StartCrawler'\n                Resource: '*'\n       \n\n  AWSCURCrawler:\n    Type: 'AWS::Glue::Crawler'\n    DependsOn:\n      - AWSCURDatabase\n      - AWSCURCrawlerComponentFunction\n    Properties:\n      Name: AWSCURCrawler-Multi-tenant\n      Description: A recurring crawler that keeps your CUR table in Athena up-to-date.\n      Role: !GetAtt AWSCURCrawlerComponentFunction.Arn\n      DatabaseName: !Ref AWSCURDatabase\n      Targets:\n        S3Targets:\n          - Path: !Sub 's3://${CURBucket}/curoutput'\n            Exclusions:\n              - '**.json'\n              - '**.yml'\n              - '**.sql'\n              - '**.csv'\n              - '**.gz'\n              - '**.zip'\n      SchemaChangePolicy:\n        UpdateBehavior: UPDATE_IN_DATABASE\n        DeleteBehavior: DELETE_FROM_DATABASE\n       \n\n  AWSCURInitializer:\n    Type: 'AWS::Lambda::Function'\n    DependsOn: AWSCURCrawler\n    Properties:\n      Code:\n        ZipFile: >\n          const AWS = require('aws-sdk');\n          const response = require('./cfn-response');\n          exports.handler = function(event, context, callback) {\n            if (event.RequestType === 'Delete') {\n              response.send(event, context, response.SUCCESS);\n            } else {\n              const glue = new AWS.Glue();\n              glue.startCrawler({ Name: 'AWSCURCrawler-Multi-tenant' }, function(err, data) {\n                if (err) {\n                  const responseData = JSON.parse(this.httpResponse.body);\n                  if (responseData['__type'] == 'CrawlerRunningException') {\n                    callback(null, responseData.Message);\n                  } else {\n                    const responseString = JSON.stringify(responseData);\n                    if (event.ResponseURL) {\n                      response.send(event, context, response.FAILED,{ msg: responseString });\n                    } else {\n                      callback(responseString);\n                    }\n                  }\n                }\n                else {\n                  if (event.ResponseURL) {\n                    response.send(event, context, response.SUCCESS);\n                  } else {\n                    callback(null, response.SUCCESS);\n                  }\n                }\n              });\n            }\n          };\n      Handler: 'index.handler'\n      Timeout: 30\n      Runtime: nodejs16.x\n      ReservedConcurrentExecutions: 1\n      Role: !GetAtt AWSCURCrawlerLambdaExecutor.Arn\n\n\n  \n  TenantCostandUsageAttributionTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: Date\n          AttributeType: N\n        - AttributeName: TenantId#ServiceName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: Date\n          KeyType: HASH\n        - AttributeName: TenantId#ServiceName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: TenantCostAndUsageAttribution\n  \n  QueryLogInsightsExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: query-log-insights-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n      Policies:\n        - PolicyName: query-log-insight-lab7\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - logs:GetQueryResults\n                  - logs:StartQuery\n                  - logs:StopQuery\n                  - logs:FilterLogEvents\n                  - logs:DescribeLogGroups\n                  - cloudformation:ListStackResources\n                Resource:\n                  - \"*\"\n              - Effect: Allow\n                Action:\n                  - s3:*\n                Resource:\n                  - !Sub 'arn:aws:s3:::${CURBucket}*'\n              - Effect: Allow\n                Action:\n                  - dynamodb:*\n                Resource:\n                  - !GetAtt TenantCostandUsageAttributionTable.Arn\n              - Effect: Allow\n                Action:\n                  - Athena:*\n                Resource:\n                  - \"*\"\n              - Effect: Allow\n                Action:\n                  - glue:*\n                Resource:\n                  - \"*\"\n                  \n  GetDynamoDBUsageAndCostByTenant:\n    Type: AWS::Serverless::Function \n    DependsOn: QueryLogInsightsExecutionRole \n    Properties:\n      CodeUri: TenantUsageAndCost/\n      Handler: tenant_usage_and_cost.calculate_daily_dynamodb_attribution_by_tenant\n      Runtime: python3.9  \n      Role: !GetAtt QueryLogInsightsExecutionRole.Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT: !Ref CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateDynamoUsageAndCostByTenant\n            Schedule: rate(5 minutes)\n  \n  GetLambdaUsageAndCostByTenant:\n    Type: AWS::Serverless::Function \n    DependsOn: QueryLogInsightsExecutionRole \n    Properties:\n      CodeUri: TenantUsageAndCost/\n      Handler: tenant_usage_and_cost.calculate_daily_lambda_attribution_by_tenant\n      Runtime: python3.9  \n      Role: !GetAtt QueryLogInsightsExecutionRole.Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT: !Ref CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateLambdaUsageAndCostByTenant\n            Schedule: rate(5 minutes)\n            \nOutputs:\n    CURBucketname:\n        Description: The name of S3 bucket name\n        Value: !Ref CURBucket\n        Export:\n          Name: \"CURBucketname\"       \n\n    AWSCURInitializerFunctionName:\n        Description: Function name of CUR initializer\n        Value: !Ref AWSCURInitializer\n        Export:\n          Name: \"AWSCURInitializerFunctionName\"     "
  },
  {
    "path": "README.md",
    "content": "# AWS Serverless SaaS Workshop\nThe goal of this workshop is to build a multi-tenant Software-as-a-Service (SaaS) solution using AWS Serverless Services, such as Amazon API Gateway, Amazon Cognito, AWS Lambda, Amazon DynamoDB, AWS CodePipeline, and Amazon CloudWatch.\n\nBy the end of this workshop you will be able to understand the challenges that are unique to a SaaS based delivery, such as onboarding, tenant isolation, data partitioning, tenant deployment pipelines, observability, and how to address them using AWS Serverless services.\n\nThis workshop is inspired by the [SaaS Factory Serverless SaaS reference solution](https://github.com/aws-samples/aws-saas-factory-ref-solution-serverless-saas). At the end of this workshop, you will build a fully functional SaaS application that will be similar to this reference solution.\n\n# Starting the workshop\nFollow this link for detailed instructions to run this workshop in your AWS Account: https://catalog.us-east-1.prod.workshops.aws/v2/workshops/b0c6ad36-0a4b-45d8-856b-8a64f0ac76bb/en-US\n\n# License\nThe documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International License. See the LICENSE file.\n\nThe sample code within this documentation is made available under the MIT-0 license. See the LICENSE-SAMPLECODE file.\n"
  },
  {
    "path": "Solution/Lab1/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab1/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab1/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab1/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab1/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/Application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab1/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: '',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'application';\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container fullscreen>\n  <mat-sidenav\n    [mode]=\"'side'\"\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <div class=\"sidebar-icon-container\">\n      <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n    </div>\n    <mat-divider></mat-divider>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <div class=\"content\" #main>\n    <router-outlet></router-outlet>\n  </div>\n  <div class=\"footer\" #footer>\n    <div class=\"footer-text\">\n      <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n      <span class=\"spacer\"></span>\n      <span>\n        Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n      </span>\n    </div>\n  </div>\n</mat-sidenav-container>\n\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {}\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { environment } from 'src/environments/environment';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = environment.apiGatewayUrl;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Edit Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Product ID</mat-label>\n          <input\n            matInput\n            value=\"{{ productId$ | async }}\"\n            [readonly]=\"true\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select formControlName=\"category\" required>\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{ category }}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router,\n    private route: ActivatedRoute\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      shardId: [],\n      productId: [],\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/products/edit/{{ element.productId }}\">\n              {{ element.name }}\n            </a>\n          </td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { environment } from 'src/environments/environment';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = environment.apiGatewayUrl;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab1/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://0grqqki7qk.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://0grqqki7qk.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab1/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab1/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab1/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c --stack-name <CloudFormation stack name>\"\n  echo \"Command to deploy server code: deployment.sh -s --stack-name <CloudFormation stack name>\"\n  echo \"Command to deploy server & client code: deployment.sh -s -c --stack-name <CloudFormation stack name>\"\n  exit 1\nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n  case $1 in\n  -s) server=1 ;;\n  -c) client=1 ;;\n  --stack-name)\n    stackname=$2\n    shift\n    ;;\n  *)\n    echo \"Unknown parameter passed: $1\"\n    exit 1\n    ;;\n  esac\n  shift\ndone\n\nif [[ -z \"$stackname\" ]]; then\n  echo \"Please provide CloudFormation stack name as parameter\"\n  echo \"Note: Invoke script without parameters to know the list of script parameters\"\n  exit 1\nfi\n\nif [[ $server -eq 1 ]]; then\n  echo \"Server code is getting deployed\"\n  cd ../server || exit # stop execution if cd fails\n  REGION=$(aws configure get region)\n\n  DEFAULT_SAM_S3_BUCKET=$(grep s3_bucket samconfig.toml | cut -d'=' -f2 | cut -d \\\" -f2)\n  echo \"aws s3 ls s3://$DEFAULT_SAM_S3_BUCKET\"\n\n  if ! aws s3 ls \"s3://${DEFAULT_SAM_S3_BUCKET}\"; then\n    echo \"S3 Bucket: $DEFAULT_SAM_S3_BUCKET specified in samconfig.toml is not readable.\n      So creating a new S3 bucket and will update samconfig.toml with new bucket name.\"\n\n    UUID=$(uuidgen | awk '{print tolower($0)}')\n    SAM_S3_BUCKET=sam-bootstrap-bucket-$UUID\n    aws s3 mb \"s3://${SAM_S3_BUCKET}\" --region \"$REGION\"\n    aws s3api put-bucket-encryption \\\n      --bucket \"$SAM_S3_BUCKET\" \\\n      --server-side-encryption-configuration '{\"Rules\": [{\"ApplyServerSideEncryptionByDefault\": {\"SSEAlgorithm\": \"AES256\"}}]}'\n    if [[ $? -ne 0 ]]; then\n      exit 1\n    fi\n    # Updating samconfig.toml with new bucket name\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' samconfig.toml\n  fi\n\n  echo \"Validating server code using pylint\"\n  python3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n\n  sam build -t template.yaml --use-container\n  sam deploy --config-file samconfig.toml --region=\"$REGION\" --stack-name=\"$stackname\"\n  cd ../scripts || exit # stop execution if cd fails\nfi\n\nif [[ $client -eq 1 ]]; then\n  echo \"Client code is getting deployed\"\n  APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='AppBucket'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\n  APP_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='APIGatewayURL'].OutputValue\" --output text)\n\n  # Configuring application UI\n\n  echo \"aws s3 ls s3://${APP_SITE_BUCKET}\"\n  if ! aws s3 ls \"s3://${APP_SITE_BUCKET}\"; then\n    echo \"Error! S3 Bucket: $APP_SITE_BUCKET not readable\"\n    exit 1\n  fi\n\n  cd ../client/Application || exit # stop execution if cd fails\n\n  echo \"Configuring environment for App Client\"\n\n  cat <<EoF >./src/environments/environment.prod.ts\nexport const environment = {\n  production: true,\n  apiGatewayUrl: '$APP_APIGATEWAYURL'\n};\nEoF\n\n  cat <<EoF >./src/environments/environment.ts\nexport const environment = {\n  production: true,\n  apiGatewayUrl: '$APP_APIGATEWAYURL'\n};\nEoF\n\n  npm install && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://${APP_SITE_BUCKET}\"\n  if ! aws s3 sync --delete --cache-control no-store dist \"s3://${APP_SITE_BUCKET}\"; then\n    exit 1\n  fi\n\n  echo \"Completed configuring environment for App Client\"\n\n  echo \"Application site URL: https://${APP_SITE_URL}\"\nfi\n"
  },
  {
    "path": "Solution/Lab1/scripts/geturl.sh",
    "content": "#!/bin/bash\nstackname=\"serverless-saas-workshop-lab1\"\n\nAPP_SITE_URL=$(aws cloudformation describe-stacks --stack-name \"$stackname\" --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\necho \"Application site URL: https://${APP_SITE_URL}\"\n"
  },
  {
    "path": "Solution/Lab1/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Solution/Lab1/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, orderId, orderName, orderProducts):\n        self.orderId = orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Solution/Lab1/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\n\ndef get_order(event, context):\n    logger.info(\"Request received to get a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.get_order(event, orderId)\n\n    logger.info(\"Request completed to get a order\")\n    \n    return utils.generate_response(order)\n    \ndef create_order(event, context):  \n    logger.info(\"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.info(\"Request completed to create a order\")\n    return utils.generate_response(order)\n    \ndef update_order(event, context):    \n    logger.info(\"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.update_order(event, payload, orderId)\n    logger.info(\"Request completed to update a order\")     \n    return utils.generate_response(order)\n\ndef delete_order(event, context):\n    logger.info(\"Request received to delete a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    response = order_service_dal.delete_order(event, orderId)\n    logger.info(\"Request completed to delete a order\")\n    return utils.create_success_response(\"Successfully deleted the order\")\n\ndef get_orders(event, context):\n    logger.info(\"Request received to get all orders\")\n    response = order_service_dal.get_orders(event)\n    logger.info(\"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab1/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\n\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_order(event, orderId):\n    \n    try:\n        response = table.get_item(Key={'orderId': orderId})\n        item = response['Item']\n        order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, orderId):\n    \n    try:\n        response = table.delete_item(Key={'orderId': orderId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    \n    order = Order(str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        })\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, orderId):\n    \n    try:\n        order = Order(orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event):\n    orders = []\n\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n                orders.append(order)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return orders\n\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Solution/Lab1/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab1/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, productId, sku, name, price, category):\n        self.productId = productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Solution/Lab1/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport product_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\n\ndef get_product(event, context):\n    logger.info(\"Request received to get a product\")\n    params = event['pathParameters']\n    productId = params['id']\n    product = product_service_dal.get_product(event, productId)\n    logger.info(\"Request completed to get a product\")    \n    return utils.generate_response(product)\n    \ndef create_product(event, context):    \n    logger.info(\"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    logger.info(payload)\n    product = product_service_dal.create_product(event, payload)\n    logger.info(\"Request completed to create a product\")\n    return utils.generate_response(product)\n    \ndef update_product(event, context):\n    logger.info(\"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.info(\"Request completed to update a product\") \n    return utils.generate_response(product)\n\ndef delete_product(event, context):\n    logger.info(\"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.info(\"Request completed to delete a product\")\n    return utils.create_success_response(\"Successfully deleted the product\")\n\ndef get_products(event, context):\n    logger.info(\"Request received to get all products\")\n    response = product_service_dal.get_products(event)\n    logger.info(\"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab1/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport logger\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_product(event, productId):\n    try:\n        response = table.get_item(Key={'productId': productId})\n        item = response['Item']\n        product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, productId):\n    try:\n        response = table.delete_item(Key={'productId': productId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    product = Product(str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }\n        )\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, productId):\n    try:\n        product = Product(productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event):    \n    products =[]\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n                products.append(product)\n                    \n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return products\n\n\n\n"
  },
  {
    "path": "Solution/Lab1/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab1/server/README.md",
    "content": "if using EE\nsam build --use-container && sam package  --output-template-file packaged.yaml --s3-bucket aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx --region us-west-2\nsam deploy --template-file packaged.yaml --config-file samconfig.toml\n\n\nNormally\nsam build -t template.yaml --use-container\nsam deploy --config-file samconfig.toml\n\n"
  },
  {
    "path": "Solution/Lab1/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n"
  },
  {
    "path": "Solution/Lab1/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\n"
  },
  {
    "path": "Solution/Lab1/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\n\n\n\n"
  },
  {
    "path": "Solution/Lab1/server/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas-lab1\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas-lab1\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\""
  },
  {
    "path": "Solution/Lab1/server/template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Lab1 - Basic Serverless Application\n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG                   \n\nParameters:\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\n    \nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-workshoplab1\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: productId\n          KeyType: HASH  \n      BillingMode: PAY_PER_REQUEST \n      TableName: Product-Lab1\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: orderId\n          KeyType: HASH  \n      BillingMode: PAY_PER_REQUEST\n      TableName: Order-Lab1\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: product-function-execution-role-lab1\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole        \n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: product-function-policy-lab1\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                  - dynamodb:Scan\n                Resource:\n                  - !GetAtt ProductTable.Arn\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  \n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      \n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n  \n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n          \n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: order-function-execution-role-lab1\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole       \n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess \n      Policies:\n        - PolicyName: order-function-policy-lab1\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                  - dynamodb:Scan\n                Resource:\n                  - !GetAtt OrderTable.Arn\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-workshop-lab1-api\n      RetentionInDays: 30\n  ApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: 'serverless-saas-workshop-lab1'\n        basePath: !Join ['', ['/', !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}              \n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                            \n      StageName: !Ref StageName            \n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayApi, \"/*/*/*\"\n          ]\n        ]            \n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distribution\"\n  AppBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: lab1-tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: lab1-tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'         \nOutputs:\n  APIGatewayURL:\n    Description: \"API Gateway endpoint URL for API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName  \n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket  "
  },
  {
    "path": "Solution/Lab2/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab2/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab2/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab2/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab2/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab2/scripts/deploy-updates.sh",
    "content": "#!/bin/bash\ncd ../server || exit # stop execution if cd fails\nrm -rf .aws-sam/\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n#Deploying shared services changes\necho \"Deploying shared services changes\" \necho Y | sam sync --stack-name serverless-saas --code --resource-id LambdaFunctions/CreateUserFunction --resource-id LambdaFunctions/RegisterTenantFunction --resource-id LambdaFunctions/GetTenantFunction -u\n\ncd ../scripts || exit\n./geturl.sh"
  },
  {
    "path": "Solution/Lab2/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c --email <email address>\"\n  echo \"Command to deploy server code: deployment.sh -s --email <email address>\"\n  echo \"Command to deploy server & client code: deployment.sh -s -c --email <email address>\"\n  exit 1\nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n  case $1 in\n  -s) server=1 ;;\n  -c) client=1 ;;\n  --email)\n    email=$2\n    shift\n    ;;\n  *)\n    echo \"Unknown parameter passed: $1\"\n    exit 1\n    ;;\n  esac\n  shift\ndone\n\n# During AWS hosted events using event engine tool\n# we pre-provision cloudfront and s3 buckets which hosts UI code.\n# So that it improves this labs total execution time.\n# Below code checks if cloudfront and s3 buckets are\n# pre-provisioned or not and then concludes if the workshop\n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false\nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  ADMIN_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminSiteBucket'].Value\" --output text)\n  LANDING_APP_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSiteBucket'].Value\" --output text)\nfi\n\nif [[ $server -eq 1 ]]; then\n  echo \"Server code is getting deployed\"\n\n  cd ../server || exit # stop execution if cd fails\n  REGION=$(aws configure get region)\n  DEFAULT_SAM_S3_BUCKET=$(grep s3_bucket samconfig.toml | cut -d'=' -f2 | cut -d \\\" -f2)\n  echo \"aws s3 ls s3://$DEFAULT_SAM_S3_BUCKET\"\n  if ! aws s3 ls \"s3://${DEFAULT_SAM_S3_BUCKET}\"; then\n    echo \"S3 Bucket: $DEFAULT_SAM_S3_BUCKET specified in samconfig.toml is not readable.\n      So creating a new S3 bucket and will update samconfig.toml with new bucket name.\"\n\n    UUID=$(uuidgen | awk '{print tolower($0)}')\n    SAM_S3_BUCKET=sam-bootstrap-bucket-$UUID\n    aws s3 mb \"s3://${SAM_S3_BUCKET}\" --region \"$REGION\"\n    aws s3api put-bucket-encryption \\\n      --bucket \"$SAM_S3_BUCKET\" \\\n      --server-side-encryption-configuration '{\"Rules\": [{\"ApplyServerSideEncryptionByDefault\": {\"SSEAlgorithm\": \"AES256\"}}]}'\n    if [[ $? -ne 0 ]]; then\n      exit 1\n    fi\n    # Updating all labs samconfig.toml with new bucket name\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab3/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab3/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab4/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab4/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab5/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab5/server/tenant-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab6/server/shared-samconfig.toml\n    ex -sc '%s/s3_bucket = .*/s3_bucket = \\\"'$SAM_S3_BUCKET'\\\"/|x' ../../Lab6/server/tenant-samconfig.toml\n  fi\n\n  echo \"Validating server code using pylint\"\n  python3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n\n  sam build -t template.yaml --use-container\n\n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file samconfig.toml --region=\"$REGION\" --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL\n  else\n    sam deploy --config-file samconfig.toml --region=\"$REGION\" --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n\n  cd ../scripts || exit # stop execution if cd fails\nfi\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  ADMIN_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminSiteBucket'].OutputValue\" --output text)\n  LANDING_APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSiteBucket'].OutputValue\" --output text)\nfi\n\nif [[ $client -eq 1 ]]; then\n  if [[ -z \"$email\" ]]; then\n    echo \"Please provide email address to setup an admin user\"\n    echo \"Note: Invoke script without parameters to know the list of script parameters\"\n    exit 1\n  fi\n  echo \"Client code is getting deployed\"\n\n  ADMIN_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminApi'].OutputValue\" --output text)\n  ADMIN_APPCLIENTID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoOperationUsersUserPoolClientId'].OutputValue\" --output text)\n  ADMIN_USERPOOL_ID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoOperationUsersUserPoolId'].OutputValue\" --output text)\n  ADMIN_USER_GROUP_NAME=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoAdminUserGroupName'].OutputValue\" --output text)\n\n  # Create admin-user in OperationUsers userpool with given input email address\n  CREATE_ADMIN_USER=$(aws cognito-idp admin-create-user \\\n  --user-pool-id \"$ADMIN_USERPOOL_ID\" \\\n  --username admin-user \\\n  --user-attributes Name=email,Value=\"$email\" Name=email_verified,Value=\"True\" Name=phone_number,Value=\"+11234567890\" Name=\"custom:userRole\",Value=\"SystemAdmin\" Name=\"custom:tenantId\",Value=\"system_admins\" \\\n  --desired-delivery-mediums EMAIL)\n\n  echo \"$CREATE_ADMIN_USER\"\n\n  # Add admin-user to admin user group\n  ADD_ADMIN_USER_TO_GROUP=$(aws cognito-idp admin-add-user-to-group \\\n    --user-pool-id \"$ADMIN_USERPOOL_ID\" \\\n    --username admin-user \\\n    --group-name \"$ADMIN_USER_GROUP_NAME\")\n\n  echo \"$ADD_ADMIN_USER_TO_GROUP\"\n\n  # Configuring admin UI\n\n  echo \"aws s3 ls s3://$ADMIN_SITE_BUCKET\"\n  if ! aws s3 ls \"s3://${ADMIN_SITE_BUCKET}\"; then\n    echo \"Error! S3 Bucket: $ADMIN_SITE_BUCKET not readable\"\n    exit 1\n  fi\n\n  cd ../client/Admin || exit # stop execution if cd fails\n\n  echo \"Configuring environment for Admin Client\"\n  cat <<EoF >./src/environments/environment.prod.ts\nexport const environment = {\n  production: true,\n  apiUrl: '$ADMIN_APIGATEWAYURL',\n};\nEoF\n\n  cat <<EoF >./src/environments/environment.ts\nexport const environment = {\n  production: false,\n  apiUrl: '$ADMIN_APIGATEWAYURL',\n};\nEoF\n\n  cat <<EoF >./src/aws-exports.ts\nconst awsmobile = {\n    \"aws_project_region\": \"$REGION\",\n    \"aws_cognito_region\": \"$REGION\",\n    \"aws_user_pools_id\": \"$ADMIN_USERPOOL_ID\",\n    \"aws_user_pools_web_client_id\": \"$ADMIN_APPCLIENTID\",\n};\n\nexport default awsmobile;\nEoF\n\n  npm install && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://${ADMIN_SITE_BUCKET}\"\n  aws s3 sync --delete --cache-control no-store dist \"s3://${ADMIN_SITE_BUCKET}\"\n\n  if [[ $? -ne 0 ]]; then\n    exit 1\n  fi\n\n  echo \"Completed configuring environment for Admin Client\"\n\n  # Configuring landing UI\n\n  echo \"aws s3 ls s3://${LANDING_APP_SITE_BUCKET}\"\n  if ! aws s3 ls \"s3://${LANDING_APP_SITE_BUCKET}\"; then\n    echo \"Error! S3 Bucket: $LANDING_APP_SITE_BUCKET not readable\"\n    exit 1\n  fi\n\n  cd ../\n\n  cd Landing || exit # stop execution if cd fails\n\n  echo \"Configuring environment for Landing Client\"\n\n  cat <<EoF >./src/environments/environment.prod.ts\nexport const environment = {\n  production: true,\n  apiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n};\nEoF\n  cat <<EoF >./src/environments/environment.ts\nexport const environment = {\n  production: false,\n  apiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n};\nEoF\n\n  npm install && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://${LANDING_APP_SITE_BUCKET}\"\n  aws s3 sync --delete --cache-control no-store dist \"s3://${LANDING_APP_SITE_BUCKET}\"\n\n  if [[ $? -ne 0 ]]; then\n    exit 1\n  fi\n\n  echo \"Completed configuring environment for Landing Client\"\n  echo \"Successfully completed deploying Admin UI and Landing UI\"\nfi\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\n"
  },
  {
    "path": "Solution/Lab2/scripts/geturl.sh",
    "content": "#!/bin/bash\nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\n"
  },
  {
    "path": "Solution/Lab2/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Solution/Lab2/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, orderId, orderName, orderProducts):\n        self.orderId = orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Solution/Lab2/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\n\ndef get_order(event, context):\n    logger.info(\"Request received to get a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.get_order(event, orderId)\n\n    logger.info(\"Request completed to get a order\")\n    \n    return utils.generate_response(order)\n    \ndef create_order(event, context):  \n    logger.info(\"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.info(\"Request completed to create a order\")\n    return utils.generate_response(order)\n    \ndef update_order(event, context):    \n    logger.info(\"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    orderId = params['id']\n    order = order_service_dal.update_order(event, payload, orderId)\n    logger.info(\"Request completed to update a order\")     \n    return utils.generate_response(order)\n\ndef delete_order(event, context):\n    logger.info(\"Request received to delete a order\")\n    params = event['pathParameters']\n    orderId = params['id']\n    response = order_service_dal.delete_order(event, orderId)\n    logger.info(\"Request completed to delete a order\")\n    return utils.create_success_response(\"Successfully deleted the order\")\n\ndef get_orders(event, context):\n    logger.info(\"Request received to get all orders\")\n    response = order_service_dal.get_orders(event)\n    logger.info(\"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab2/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\n\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_order(event, orderId):\n    \n    try:\n        response = table.get_item(Key={'orderId': orderId})\n        item = response['Item']\n        order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, orderId):\n    \n    try:\n        response = table.delete_item(Key={'orderId': orderId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    \n    order = Order(str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        })\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, orderId):\n    \n    try:\n        order = Order(orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event):\n    orders = []\n\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                order = Order(item['orderId'], item['orderName'], item['orderProducts'])\n                orders.append(order)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return orders\n\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Solution/Lab2/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab2/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, productId, sku, name, price, category):\n        self.productId = productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Solution/Lab2/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport product_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\n\ndef get_product(event, context):\n    logger.info(\"Request received to get a product\")\n    params = event['pathParameters']\n    productId = params['id']\n    product = product_service_dal.get_product(event, productId)\n    logger.info(\"Request completed to get a product\")    \n    return utils.generate_response(product)\n    \ndef create_product(event, context):    \n    logger.info(\"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    logger.info(payload)\n    product = product_service_dal.create_product(event, payload)\n    logger.info(\"Request completed to create a product\")\n    return utils.generate_response(product)\n    \ndef update_product(event, context):\n    logger.info(\"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.info(\"Request completed to update a product\") \n    return utils.generate_response(product)\n\ndef delete_product(event, context):\n    logger.info(\"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.info(\"Request completed to delete a product\")\n    return utils.create_success_response(\"Successfully deleted the product\")\n\ndef get_products(event, context):\n    logger.info(\"Request received to get all products\")\n    response = product_service_dal.get_products(event)\n    logger.info(\"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab2/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport logger\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\ndef get_product(event, productId):\n    try:\n        response = table.get_item(Key={'productId': productId})\n        item = response['Item']\n        product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, productId):\n    try:\n        response = table.delete_item(Key={'productId': productId})\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    product = Product(str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }\n        )\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, productId):\n    try:\n        product = Product(productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\")\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event):    \n    products =[]\n    try:\n        response = table.scan()    \n        if (len(response['Items']) > 0):\n            for item in response['Items']:\n                product = Product(item['productId'], item['sku'], item['name'], item['price'], item['category'])\n                products.append(product)\n                    \n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return products\n\n\n\n"
  },
  {
    "path": "Solution/Lab2/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab2/server/README.md",
    "content": "if using EE\nsam build --use-container && sam package  --output-template-file packaged.yaml --s3-bucket aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx --region us-west-2\nsam deploy --template-file packaged.yaml --config-file samconfig.toml\n\n\nNormally\nsam build -t template.yaml --use-container\nsam deploy --config-file samconfig.toml"
  },
  {
    "path": "Solution/Lab2/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Solution/Lab2/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    userpool_id = user_pool_operation_user\n    appclient_id = app_client_operation_user  \n   \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    policy.allowAllMethods()\n    \n    authResponse = policy.build()\n \n    context = {\n        'userName': user_name,\n        'userPoolId': userpool_id\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab2/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Solution/Lab2/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport requests\n\nregion = os.environ['AWS_REGION']\n\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\n#This method has been locked down to be only called from tenant registration service\ndef create_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n\n    try:          \n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],                    \n                    'isActive': True                    \n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\ndef update_tenant(event, context):\n    \n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    \n    logger.info(\"Request received to update tenant\")\n    \n    response_update = table_tenant_details.update_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n        ExpressionAttributeValues={\n                ':tenantName' : tenant_details['tenantName'],\n                ':tenantAddress': tenant_details['tenantAddress'],\n                ':tenantEmail': tenant_details['tenantEmail'],\n                ':tenantPhone': tenant_details['tenantPhone'],\n                ':tenantTier': tenant_details['tenantTier']                \n            },\n        ReturnValues=\"UPDATED_NEW\"\n        )             \n                \n    logger.info(response_update)     \n\n    logger.info(\"Request completed to update tenant\")\n    return utils.create_success_response(\"Tenant Updated\")    \n\ndef get_tenant(event, context):\n    tenant_id = event['pathParameters']['tenantid']    \n    logger.info(\"Request received to get tenant details\")\n    \n    tenant_details = table_tenant_details.get_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        AttributesToGet=[\n            'tenantName',\n            'tenantAddress',\n            'tenantEmail',\n            'tenantPhone'\n        ]    \n    )             \n    item = tenant_details['Item']\n    tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n    logger.info(tenant_info)\n    \n    logger.info(\"Request completed to get tenant details\")\n    return utils.create_success_response(tenant_info.__dict__)\n\ndef deactivate_tenant(event, context):\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    tenant_id = event['pathParameters']['tenantid']\n    \n    logger.info(\"Request received to deactivate tenant\")\n\n    response = table_tenant_details.update_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        UpdateExpression=\"set isActive = :isActive\",\n        ExpressionAttributeValues={\n                ':isActive': False\n            },\n        ReturnValues=\"ALL_NEW\"\n    )             \n    \n    logger.info(response)\n\n    update_user_response = __invoke_disable_users(headers, auth, host, stage_name, url_disable_users, tenant_id)\n    logger.info(update_user_response)\n\n    logger.info(\"Request completed to deactivate tenant\")\n    return utils.create_success_response(\"Tenant Deactivated\")\n\ndef activate_tenant(event, context):\n    \n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    tenant_id = event['pathParameters']['tenantid']\n    \n    logger.info(\"Request received to activate tenant\")\n\n    response = table_tenant_details.update_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        UpdateExpression=\"set isActive = :isActive\",\n        ExpressionAttributeValues={\n                ':isActive': True\n            },\n        ReturnValues=\"ALL_NEW\"\n    )             \n    \n    logger.info(response)\n\n    update_user_response = __invoke_enable_users(headers, auth, host, stage_name, url_enable_users, tenant_id)\n    logger.info(update_user_response)\n\n    logger.info(\"Request completed to activate tenant\")\n    return utils.create_success_response(\"Tenant activated\")\n    \ndef __invoke_disable_users(headers, auth, host, stage_name, invoke_url, tenant_id):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/', tenant_id])\n        response = requests.put(url, auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_enable_users(headers, auth, host, stage_name, invoke_url, tenant_id):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/', tenant_id])\n        response = requests.put(url, auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Solution/Lab2/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\n\nlambda_client = boto3.client('lambda')\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n\n        tenant_details['tenantId'] = tenant_id\n\n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Solution/Lab2/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport utils\nfrom boto3.dynamodb.conditions import Key\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\nuser_pool_id = os.environ['TENANT_USER_POOL_ID']\n\ndef create_tenant_admin_user(event, context):\n    logger.info(event)\n    app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    user_details = json.loads(event['body'])\n\n    logger.info(\"Request received to create new user\")\n    logger.info(event)    \n    \n    tenant_id = user_details['tenantId']\n    \n    response = client.admin_create_user(\n        Username=user_details['userName'],\n        UserPoolId=user_pool_id,\n        ForceAliasCreation=True,\n        UserAttributes=[\n            {\n                'Name': 'email',\n                'Value': user_details['userEmail']\n            },\n            {\n                'Name': 'email_verified',\n                'Value': 'true'\n            },\n            {\n                'Name': 'custom:userRole',\n                'Value': user_details['userRole'] \n            },            \n            {\n                'Name': 'custom:tenantId',\n                'Value': tenant_id\n            }\n        ]\n    )\n    \n    logger.info(response)\n    user_mgmt = UserManagement()\n    user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], tenant_id)\n    response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], tenant_id)\n\n    logger.info(\"Request completed to create new user \")\n    return utils.create_success_response(\"New user created\")\n    \ndef get_users(event, context):\n    users = []  \n    logger.info(\"Request received to get users\")\n    logger.info(event) \n    \n    response = client.list_users(\n        UserPoolId=user_pool_id\n    )\n    logger.info(response) \n    num_of_users = len(response['Users'])\n    if (num_of_users > 0):\n        for user in response['Users']:\n            user_info = UserInfo()\n            for attr in user[\"Attributes\"]:\n                if(attr[\"Name\"] == \"custom:tenantId\"):\n                    user_info.tenant_id = attr[\"Value\"]\n\n                if(attr[\"Name\"] == \"custom:userRole\"):\n                    user_info.user_role = attr[\"Value\"]\n\n                if(attr[\"Name\"] == \"email\"):\n                    user_info.email = attr[\"Value\"] \n            user_info.enabled = user[\"Enabled\"]\n            user_info.created = user[\"UserCreateDate\"]\n            user_info.modified = user[\"UserLastModifiedDate\"]\n            user_info.status = user[\"UserStatus\"] \n            user_info.user_name = user[\"Username\"]\n            users.append(user_info)                    \n    # return an empty list when there are no users otherwise will result in API Gateway error\n    return utils.generate_response(users)\n    \n   \n\ndef get_user(event, context):\n    user_name = event['pathParameters']['username']  \n\n    logger.info(\"Request received to get user\")\n    \n    user_info = get_user_info(user_pool_id, user_name)\n    logger.info(\"Request completed to get new user \")\n    return utils.create_success_response(user_info.__dict__)\n\ndef update_user(event, context):\n    user_details = json.loads(event['body'])\n    user_name = event['pathParameters']['username']    \n\n    logger.info(\"Request received to update user\")\n\n    response = client.admin_update_user_attributes(\n        Username=user_name,\n        UserPoolId=user_pool_id,\n        UserAttributes=[\n            {\n                'Name': 'email',\n                'Value': user_details['userEmail']\n            },\n            {\n                'Name': 'custom:userRole',\n                'Value': user_details['userRole'] \n            }\n        ]\n    )\n    logger.info(response)\n\n    logger.info(\"Request completed to update user\")\n        \n    return utils.create_success_response(\"user updated\")    \n\ndef disable_user(event, context):\n    user_name = event['pathParameters']['username']\n\n    logger.info(\"Request received to disable new user\")\n    \n    response = client.admin_disable_user(\n        Username=user_name,\n        UserPoolId=user_pool_id\n    )\n        \n    logger.info(response)\n    logger.info(\"Request completed to disable new user\")\n    return utils.create_success_response(\"User disabled\")\n    \n#This method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    logger.info(event)    \n    \n    tenantid_to_update = event['tenantid']\n    \n    filtering_exp = Key('tenantId').eq(tenantid_to_update)\n    response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n    users = response.get('Items')\n    \n    for user in users:\n        response = client.admin_disable_user(\n            Username=user['userName'],\n            UserPoolId=user_pool_id\n        )\n        \n    logger.info(response)\n    logger.info(\"Request completed to disable users\")\n    return utils.create_success_response(\"Users disabled\")\n    \n\n#This method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    logger.info(event)    \n    \n    tenantid_to_update = event['tenantid']\n    \n    filtering_exp = Key('tenantId').eq(tenantid_to_update)\n    response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n    users = response.get('Items')\n    \n    for user in users:\n        response = client.admin_enable_user(\n            Username=user['userName'],\n            UserPoolId=user_pool_id\n        )\n        \n    logger.info(response)\n    logger.info(\"Request completed to enable users\")\n    return utils.create_success_response(\"Users enables\")\n\ndef get_user_info(user_pool_id, user_name):\n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.info(response)\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.info(user_info)        \n    return user_info    \n\nclass UserManagement:\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Solution/Lab2/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n"
  },
  {
    "path": "Solution/Lab2/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab2/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth\n\ndef get_headers(event):\n    return event['headers']\n"
  },
  {
    "path": "Solution/Lab2/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn            \n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable/{tenantid}:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                requestTemplates:\n                  application/json: \"{\\\"tenantid\\\": \\\"$input.params('tenantid')\\\" }\"\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable/{tenantid}:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                requestTemplates:\n                  application/json: \"{ \\\"tenantid\\\": \\\"$input.params('tenantid')\\\" }\"\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n  \nOutputs:  \n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Solution/Lab2/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  \n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  "
  },
  {
    "path": "Solution/Lab2/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\"  \nResources:  \n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Thanks for signing up. \"\n              - \"You username is {username} and temporary password is {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - https://example.com\n      LogoutURLs:\n        - https://example.com\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:  \n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL\n  CognitoAdminUserGroupName:\n    Value: !Ref CognitoAdminUserGroup  "
  },
  {
    "path": "Solution/Lab2/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String  \n  TenantDetailsTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String  \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  \n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  \n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId          \n          \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"\n      \n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"   \n      \n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n      \n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n      \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n      \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n      \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']]               \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n      \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n  \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"          \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n   \n  \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  "
  },
  {
    "path": "Solution/Lab2/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST \n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE              \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL          \nOutputs:\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Solution/Lab2/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]     \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'    \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName  \n    Condition: IsNotRunningInEventEngine  \n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName     \n    Condition: IsNotRunningInEventEngine\n         "
  },
  {
    "path": "Solution/Lab2/server/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\""
  },
  {
    "path": "Solution/Lab2/server/template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Lab2 - Bootstrap common resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]    \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n  CognitoOperationUsersUserPoolId:\n    Description: The user pool id of Admin Management userpool \n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n  CognitoAdminUserGroupName:\n    Description: The Admin Management userpool admin user group name\n    Value: !GetAtt Cognito.Outputs.CognitoAdminUserGroupName    \n    "
  },
  {
    "path": "Solution/Lab3/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab3/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab3/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab3/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab3/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Solution/Lab3/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Solution/Lab3/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Solution/Lab3/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Solution/Lab3/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle *ngIf=\"tenantNameRequired\">Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\" *ngIf=\"tenantNameRequired\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid && tenantNameRequired\"\n              >\n                <div *ngIf=\"tenantNameRequired; else loginTextBlock\">Submit</div>\n                <ng-template #loginTextBlock>Login</ng-template>\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n  tenantNameRequired: boolean = true;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {\n    if (\n      'userPoolId' in environment &&\n      'appClientId' in environment &&\n      'apiGatewayUrl' in environment\n    ) {\n      // If a tenant's cognito configuration is provided in the\n      // \"environment\" object, then we take that instead of asking\n      // the visitor to provide the name of their tenant in order\n      // to do a look-up for that tenant's cognito configuration.\n      localStorage.setItem('tenantName', 'PooledTenants');\n      localStorage.setItem('userPoolId', (environment as any).userPoolId);\n      localStorage.setItem('appClientId', (environment as any).appClientId);\n      localStorage.setItem('apiGatewayUrl', (environment as any).apiGatewayUrl);\n      this.tenantNameRequired = false;\n    }\n  }\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    if (!this.tenantNameRequired) {\n      this.router.navigate(['/dashboard']);\n      return true;\n    }\n\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.shardId }}:{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Edit Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Product ID</mat-label>\n          <input\n            matInput\n            value=\"{{ productId$ | async }}\"\n            [readonly]=\"true\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select formControlName=\"category\" required>\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{ category }}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router,\n    private route: ActivatedRoute\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      shardId: [],\n      productId: [],\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/products/edit/{{ element.shardId }}:{{ element.productId }}\">\n              {{ element.name }}\n            </a>\n          </td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Solution/Lab3/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab3/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab3/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab3/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab3/client/dummy.txt",
    "content": ""
  },
  {
    "path": "Solution/Lab3/scripts/deploy-updates.sh",
    "content": "#!/bin/bash\ncd ../server || exit # stop execution if cd fails\nrm -rf .aws-sam/\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n#Deploying shared services changes\necho \"Deploying shared services changes\"  \necho Y | sam sync --stack-name serverless-saas -t shared-template.yaml --code --resource-id LambdaFunctions/ServerlessSaaSLayers --resource-id LambdaFunctions/SharedServicesAuthorizerFunction -u\n\n#Deploying tenant services changes\necho \"Deploying tenant services changes\"\nrm -rf .aws-sam/\necho Y | sam sync --stack-name stack-pooled -t tenant-template.yaml --code --resource-id ServerlessSaaSLayers --resource-id BusinessServicesAuthorizerFunction --resource-id CreateProductFunction -u\n\ncd ../scripts || exit\n./geturl.sh"
  },
  {
    "path": "Solution/Lab3/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c\"\n  echo \"Command to deploy bootstrap server code: deployment.sh -b\"\n  echo \"Command to deploy tenant server code: deployment.sh -t\"\n  echo \"Command to deploy bootstrap & tenant server code: deployment.sh -s\" \n  echo \"Command to deploy server & client code: deployment.sh -s -c\"\n  exit 1      \nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        -s) server=1 ;;\n        -b) bootstrap=1 ;;\n        -t) tenant=1 ;;\n        -c) client=1 ;;\n        *) echo \"Unknown parameter passed: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSiteBucket'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Validating server code using pylint\"\n  cd ../server\n  python3 -m pylint -E -d E0401,E1111 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n  cd ../scripts\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]]; then\n  echo \"Bootstrap server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t shared-template.yaml --use-container\n  \n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\n  else\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n  cd ../scripts\nfi  \n\nif [[ $server -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Tenant server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t tenant-template.yaml --use-container\n  sam deploy --config-file tenant-samconfig.toml --region=$REGION\n  cd ../scripts\nfi\n\n\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSiteBucket'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\nif [[ $client -eq 1 ]]; then\n  echo \"Client code is getting deployed\"\n  ADMIN_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminApi'].OutputValue\" --output text)\n  APP_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name stack-pooled --query \"Stacks[0].Outputs[?OutputKey=='TenantAPI'].OutputValue\" --output text)\n  APP_APPCLIENTID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoTenantAppClientId'].OutputValue\" --output text)\n  APP_USERPOOLID=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='CognitoTenantUserPoolId'].OutputValue\" --output text)\n\n\n  # Admin UI and Landing UI are configured in Lab2 \n  echo \"Admin UI and Landing UI are configured in Lab2. Only App UI will be configured in this Lab3.\"\n  # Configuring app UI \n\n  echo \"aws s3 ls s3://$APP_SITE_BUCKET\"\n  aws s3 ls s3://$APP_SITE_BUCKET \n  if [ $? -ne 0 ]; then\n      echo \"Error! S3 Bucket: $APP_SITE_BUCKET not readable\"\n      exit 1\n  fi\n\n  cd ../client/Application\n\n  echo \"Configuring environment for App Client\"\n\n  cat << EoF > ./src/environments/environment.prod.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL',\n    apiGatewayUrl: '$APP_APIGATEWAYURL',\n    userPoolId: '$APP_USERPOOLID',\n    appClientId: '$APP_APPCLIENTID',\n  };\nEoF\n  cat << EoF > ./src/environments/environment.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL',\n    apiGatewayUrl: '$APP_APIGATEWAYURL',\n    userPoolId: '$APP_USERPOOLID',\n    appClientId: '$APP_APPCLIENTID',\n  };\nEoF\n\n  npm install --legacy-peer-deps && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET\"\n  aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET \n\n  if [[ $? -ne 0 ]]; then\n      exit 1\n  fi\n\n  echo \"Completed configuring environment for App Client\"\n  echo \"Successfully completed deploying Application UI\"\nfi  \n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\"\n"
  },
  {
    "path": "Solution/Lab3/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Solution/Lab3/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Solution/Lab3/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Solution/Lab3/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab3/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\nimport metrics_manager\n\ntable_name = os.environ['ORDER_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\nsuffix_start = 1 \nsuffix_end = 10\n \ndef get_order(event, key):\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\",\n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Solution/Lab3/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab3/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Solution/Lab3/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab3/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport random\nimport threading\nimport metrics_manager\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ntable = dynamodb.Table(table_name)\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']    \n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, \n                ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", \n        ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):    \n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n"
  },
  {
    "path": "Solution/Lab3/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab3/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml"
  },
  {
    "path": "Solution/Lab3/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Solution/Lab3/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport utils\nimport auth_manager\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\ntenant_userpool_id = os.environ['TENANT_USER_POOL']\ntenant_appclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        userpool_id = tenant_userpool_id\n        appclient_id = tenant_appclient_id \n    \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n\n    authResponse = policy.build()\n \n    context = {\n        'userName': user_name,\n        'userPoolId': userpool_id,\n        'tenantId': tenant_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab3/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\n\nuserpool_id = os.environ['TENANT_USER_POOL']\nappclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n     \n    context = {\n        'userName': user_name,\n        'tenantId': tenant_id        \n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab3/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Solution/Lab3/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport requests\nimport metrics_manager\nimport auth_manager\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nregion = os.environ['AWS_REGION']\n\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\n#This method has been locked down to be only called from tenant registration service\ndef create_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n\n    try:          \n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],                    \n                    'isActive': True                    \n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        \n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier']\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()   \n    \ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Solution/Lab3/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\n\nlambda_client = boto3.client('lambda')\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n\n        tenant_details['tenantId'] = tenant_id\n\n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Solution/Lab3/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport metrics_manager\nimport auth_manager\nimport utils\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\nuser_pool_id = os.environ['TENANT_USER_POOL_ID']\n\ndef create_tenant_admin_user(event, context):\n    app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']        \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user \")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n    \n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                   \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n        user_info = get_user_info(event, user_pool_id, user_name)\n        logger.log_with_tenant_context(event, \"Request completed to get new user \")\n        return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")        \n        return utils.create_unauthorized_response()\n    else:\n        metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)            \n        response = client.admin_update_user_attributes(\n            Username=user_name,\n            UserPoolId=user_pool_id,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                }\n            ]\n        )\n        logger.log_with_tenant_context(event, response)\n\n        logger.log_with_tenant_context(event, \"Request completed to update user\")\n        \n        return utils.create_success_response(\"user updated\")    \n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n        response = client.admin_disable_user(\n            Username=user_name,\n            UserPoolId=user_pool_id\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n        return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n    \n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info    \n\nclass UserManagement:\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Solution/Lab3/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\n"
  },
  {
    "path": "Solution/Lab3/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Solution/Lab3/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Solution/Lab3/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab3/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth\n\ndef get_headers(event):\n    return event['headers']\n"
  },
  {
    "path": "Solution/Lab3/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn            \n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n  \nOutputs:  \n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Solution/Lab3/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  \n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  "
  },
  {
    "path": "Solution/Lab3/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\" \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nResources:  \n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:  \n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Solution/Lab3/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String  \n  TenantDetailsTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String  \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  \n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  \n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n          TENANT_USER_POOL: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT: !Ref CognitoUserPoolClientId          \n          \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"\n      \n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"   \n      \n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n      \n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n      \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n      \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n      \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']]               \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n      \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n  \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"          \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n   \n  \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  "
  },
  {
    "path": "Solution/Lab3/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST \n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE              \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL          \nOutputs:\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Solution/Lab3/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'          \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName  \n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName  \n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName     \n    Condition: IsNotRunningInEventEngine"
  },
  {
    "path": "Solution/Lab3/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\""
  },
  {
    "path": "Solution/Lab3/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]    \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  "
  },
  {
    "path": "Solution/Lab3/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\n\n"
  },
  {
    "path": "Solution/Lab3/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies-pooled\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Product-pooled\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Order-pooled\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-product-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-product-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt ProductTable.Arn\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      \n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-order-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-order-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt OrderTable.Arn\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  #Tenant Authorizer\n  TenantAuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: tenant-authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: tenant-authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n    \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: TenantAuthorizerExecutionRole\n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt TenantAuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL: !ImportValue Serverless-SaaS-CognitoTenantUserPoolId\n          TENANT_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoTenantAppClientId\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-tenant-api-pooled\n      RetentionInDays: 30\n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: 'pooled-serverless-saas-tenant-api'\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n      \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Solution/Lab4/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab4/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab4/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab4/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab4/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Solution/Lab4/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Solution/Lab4/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Solution/Lab4/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Solution/Lab4/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { environment } from 'src/environments/environment';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle *ngIf=\"tenantNameRequired\">Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\" *ngIf=\"tenantNameRequired\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid && tenantNameRequired\"\n              >\n                <div *ngIf=\"tenantNameRequired; else loginTextBlock\">Submit</div>\n                <ng-template #loginTextBlock>Login</ng-template>\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n  tenantNameRequired: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {\n    if (\n      environment.userPoolId &&\n      environment.appClientId &&\n      environment.apiGatewayUrl\n    ) {\n      // If a tenant's cognito configuration is provided in the\n      // \"environment\" object, then we take that instead of asking\n      // the visitor to provide the name of their tenant in order\n      // to do a look-up for that tenant's cognito configuration.\n      localStorage.setItem('tenantName', 'PooledTenants');\n      localStorage.setItem('userPoolId', environment.userPoolId);\n      localStorage.setItem('appClientId', environment.appClientId);\n      localStorage.setItem('apiGatewayUrl', environment.apiGatewayUrl);\n      this.tenantNameRequired = false;\n    }\n  }\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    if (!this.tenantNameRequired) {\n      this.router.navigate(['/dashboard']);\n      return true;\n    }\n\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.shardId }}:{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm!\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Description</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"description\"\n            placeholder=\"Enter product description\"\n          />\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button mat-raised-button color=\"primary\" (click)=\"(submit)\">\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup | undefined;\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private route: ActivatedRoute,\n    private router: Router,\n    private productSvc: ProductService,\n    private fb: FormBuilder\n  ) {}\n\n  ngOnInit(): void {\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.productForm = this.fb.group({\n      productId: [''],\n      name: [''],\n      price: [''],\n      description: [''],\n    });\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.name }}</td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  regApiGatewayUrl:\n    'https://3vby5hwma9.execute-api.us-west-2.amazonaws.com/prod/',\n  apiGatewayUrl: 'https://hj4p6t6ob5.execute-api.us-west-2.amazonaws.com/prod/',\n  userPoolId: 'us-west-2_HAYgKc4Ws',\n  appClientId: '7e7hpl565vdrvkf4ief77bgnm',\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://k1lecgl9ye.execute-api.us-west-2.amazonaws.com/prod/',\n  apiGatewayUrl: 'https://885cs6u6m8.execute-api.us-west-2.amazonaws.com/prod/',\n  userPoolId: 'us-west-2_bCwYPJqrb',\n  appClientId: '6lv6qhvmh6ivgd94qsftruc994',\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Solution/Lab4/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab4/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab4/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab4/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab4/client/dummy.txt",
    "content": ""
  },
  {
    "path": "Solution/Lab4/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c\"\n  echo \"Command to deploy bootstrap server code: deployment.sh -b\"\n  echo \"Command to deploy tenant server code: deployment.sh -t\"\n  echo \"Command to deploy bootstrap & tenant server code: deployment.sh -s\" \n  echo \"Command to deploy server & client code: deployment.sh -s -c\"\n  exit 1      \nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        -s) server=1 ;;\n        -b) bootstrap=1 ;;\n        -t) tenant=1 ;;\n        -c) client=1 ;;\n        *) echo \"Unknown parameter passed: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Validating server code using pylint\"\n  cd ../server\n  python3 -m pylint -E -d E0401,E1111 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n  cd ../scripts\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]]; then\n  echo \"Bootstrap server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t shared-template.yaml --use-container\n  \n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\n  else\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n    \n  cd ../scripts\nfi  \n\nif [[ $server -eq 1 ]] || [[ $tenant -eq 1 ]]; then\n  echo \"Tenant server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  sam build -t tenant-template.yaml --use-container\n  sam deploy --config-file tenant-samconfig.toml --region=$REGION\n  cd ../scripts\nfi\n\nif [[ $client -eq 1 ]]; then\n  # Admin UI and Landing UI are configured in Lab2 \n  # App UI is configured in Lab3\n  echo \"Admin UI and Landing UI are configured in Lab2. App UI is configured in Lab3.\n  So, no UI code is built in this Lab4\"\n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n    ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n    LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n    APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\n  fi\n  \n\n\n  echo \"Admin site URL: https://$ADMIN_SITE_URL\"\n  echo \"Landing site URL: https://$LANDING_APP_SITE_URL\"\n  echo \"App site URL: https://$APP_SITE_URL\"\n  \nfi  \n\n"
  },
  {
    "path": "Solution/Lab4/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Solution/Lab4/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Solution/Lab4/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Solution/Lab4/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab4/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\nimport metrics_manager\n\ntable_name = os.environ['ORDER_TABLE_NAME']\n\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n \ndef get_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    table = __get_dynamodb_table(event, dynamodb)\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" \n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n    dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )        \n        \n    return dynamodb.Table(table_name)\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Solution/Lab4/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab4/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Solution/Lab4/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab4/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport random\nimport threading\nimport metrics_manager\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\ntable_name = os.environ['PRODUCT_TABLE_NAME']\n\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):  \n    table = __get_dynamodb_table(event, dynamodb)  \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):    \n    table = __get_dynamodb_table(event, dynamodb)\n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']  \n    table = __get_dynamodb_table(event, dynamodb)  \n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):    \n    table = __get_dynamodb_table(event, dynamodb)\n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)    \n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" \n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n    dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )        \n        \n    return dynamodb.Table(table_name)"
  },
  {
    "path": "Solution/Lab4/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab4/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml\n\n"
  },
  {
    "path": "Solution/Lab4/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Solution/Lab4/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport utils\nimport auth_manager\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\ntenant_userpool_id = os.environ['TENANT_USER_POOL']\ntenant_appclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        userpool_id = tenant_userpool_id\n        appclient_id = tenant_appclient_id \n    \n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n\n    authResponse = policy.build()\n\n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.SHARED_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n \n    context = {\n        'accesskey': credentials['AccessKeyId'], \n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'userPoolId': userpool_id,\n        'tenantId': tenant_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab4/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\n\nuserpool_id = os.environ['TENANT_USER_POOL']\nappclient_id = os.environ['TENANT_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n        \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.BUSINESS_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'userRole': user_role        \n    }\n    \n    authResponse['context'] = context\n    \n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab4/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Solution/Lab4/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport requests\nimport metrics_manager\nimport auth_manager\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nregion = os.environ['AWS_REGION']\n\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\n#This method has been locked down to be only called from tenant registration service\ndef create_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n\n    try:          \n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],                    \n                    'isActive': True                    \n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        \n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier']\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        update_details = {}\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()   \n    \ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url, '/'])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Solution/Lab4/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\n\nlambda_client = boto3.client('lambda')\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n\n        tenant_details['tenantId'] = tenant_id\n\n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Solution/Lab4/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport metrics_manager\nimport auth_manager\nimport utils\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\nuser_pool_id = os.environ['TENANT_USER_POOL_ID']\n\ndef create_tenant_admin_user(event, context):\n    app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']        \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user\")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n    \n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            logger.log_with_tenant_context(event, \"Request completed to get new user\")\n            return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)\n            response = client.admin_update_user_attributes(\n                Username=user_name,\n                UserPoolId=user_pool_id,\n                UserAttributes=[\n                    {\n                        'Name': 'email',\n                        'Value': user_details['userEmail']\n                    },\n                    {\n                        'Name': 'custom:userRole',\n                        'Value': user_details['userRole'] \n                    }\n                ]\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to update user\")\n            return utils.create_success_response(\"user updated\")    \n\n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n            response = client.admin_disable_user(\n                Username=user_name,\n                UserPoolId=user_pool_id\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n            return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n    \n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    tenantid_to_update = event['tenantId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info\n\nclass UserManagement:\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Solution/Lab4/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\ndef getPolicyForUser(user_role, service_identifier, tenant_id, region, aws_account_id):\n    \"\"\" This method is being used by Authorizer to get appropriate policy by user role\n    Args:\n        user_role (string): UserRoles enum\n        tenant_id (string): \n        region (string): \n        aws_account_id (string):  \n    Returns:\n        string: policy that tenant needs to assume\n    \"\"\"\n    iam_policy = \"\"\n    \n    if (isSystemAdmin(user_role)):\n        iam_policy = __getPolicyForSystemAdmin(region, aws_account_id)\n    elif (isTenantAdmin(user_role)):\n        iam_policy = __getPolicyForTenantAdmin(tenant_id, service_identifier, region, aws_account_id)\n    elif (isTenantUser(user_role)):\n        iam_policy = __getPolicyForTenantUser(tenant_id, region, aws_account_id)\n    \n    return iam_policy\n\ndef __getPolicyForSystemAdmin(region, aws_account_id):\n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                    \"dynamodb:UpdateItem\",\n                    \"dynamodb:GetItem\",\n                    \"dynamodb:PutItem\",\n                    \"dynamodb:DeleteItem\",\n                    \"dynamodb:Query\",          \n                    \"dynamodb:Scan\"\n                  ],\n                  \"Resource\": [\n                       \"arn:aws:dynamodb:{0}:{1}:table/*\".format(region, aws_account_id),\n                  ]\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n    \ndef __getPolicyForTenantAdmin(tenant_id, sevice_identifier, region, aws_account_id):\n    if (sevice_identifier == utils.Service_Identifier.SHARED_SERVICES.value):\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:Query\"                   \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantUserMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantDetails\".format(region, aws_account_id)\n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringEquals\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"               \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantStackMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-Settings\".format(region, aws_account_id)\n                    ]\n                }\n            ]\n        }\n    else:\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"    \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"       \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                }\n            ]\n        }\n    return json.dumps(policy)\n\ndef __getPolicyForTenantUser(tenant_id, region, aws_account_id):\n    \n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"      \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              },\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"               \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              }\n          ]\n        }\n    \n    return json.dumps(policy)"
  },
  {
    "path": "Solution/Lab4/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    logger.error (log_message)\n\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Solution/Lab4/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Solution/Lab4/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab4/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n\nclass Service_Identifier(Enum):\n    SHARED_SERVICES     = \"SharedServices\"\n    BUSINESS_SERVICES    = \"BusinessServices\"\n    \ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth\n\ndef get_headers(event):\n    return event['headers']\n"
  },
  {
    "path": "Solution/Lab4/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn            \n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n  \nOutputs:  \n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Solution/Lab4/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  \n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  "
  },
  {
    "path": "Solution/Lab4/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\" \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nResources:  \n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True\n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:  \n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Solution/Lab4/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String  \n  TenantDetailsTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String  \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  \n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn \n  AuthorizerAccessRole:\n    Type: AWS::IAM::Role\n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      RoleName: authorizer-access-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS:\n                - !GetAtt 'AuthorizerExecutionRole.Arn'\n            Action:\n              - sts:AssumeRole       \n      Policies:\n        - PolicyName: authorizer-access-role-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:BatchGetItem     \n                  - dynamodb:GetItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Query\n                  - dynamodb:Scan     \n                Resource:  \n                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*                   \n  \n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerAccessRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n          TENANT_USER_POOL: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT: !Ref CognitoUserPoolClientId          \n          \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"\n      \n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"   \n      \n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n      \n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n      \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n      \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n      \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']]               \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n      \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n  \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"          \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n   \n  \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn\n  AuthorizerExecutionRoleArn:\n    Value: !GetAtt AuthorizerExecutionRole.Arn        \n  "
  },
  {
    "path": "Solution/Lab4/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST \n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE              \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL          \nOutputs:\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Solution/Lab4/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'          \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName  \n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName  \n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName     \n    Condition: IsNotRunningInEventEngine"
  },
  {
    "path": "Solution/Lab4/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\""
  },
  {
    "path": "Solution/Lab4/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]   \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  \n  AuthorizerExecutionRoleArn:\n    Description: The Lambda authorizer execution role\n    Value: !GetAtt LambdaFunctions.Outputs.AuthorizerExecutionRoleArn \n    Export:\n      Name: \"Serverless-SaaS-AuthorizerExecutionRoleArn\"   "
  },
  {
    "path": "Solution/Lab4/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\n\n"
  },
  {
    "path": "Solution/Lab4/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies-pooled\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Product-pooled\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: Order-pooled\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-product-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-product-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt ProductTable.Arn\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n  \n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      \n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: pooled-order-function-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: pooled-order-function-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:Query\n                Resource:\n                  - !GetAtt OrderTable.Arn\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n      \n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          ORDER_TABLE_NAME: !Ref OrderTable\n  \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !ImportValue Serverless-SaaS-AuthorizerExecutionRoleArn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL: !ImportValue Serverless-SaaS-CognitoTenantUserPoolId\n          TENANT_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoTenantAppClientId\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-tenant-api-pooled\n      RetentionInDays: 30\n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: 'pooled-serverless-saas-tenant-api'\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n      \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Solution/Lab5/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab5/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab5/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab5/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab5/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Solution/Lab5/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Solution/Lab5/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Solution/Lab5/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Solution/Lab5/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle *ngIf=\"tenantNameRequired\">Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\" *ngIf=\"tenantNameRequired\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid && tenantNameRequired\"\n              >\n                <div *ngIf=\"tenantNameRequired; else loginTextBlock\">Submit</div>\n                <ng-template #loginTextBlock>Login</ng-template>\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n  tenantNameRequired: boolean = true;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {\n    if (\n      'userPoolId' in environment &&\n      'appClientId' in environment &&\n      'apiGatewayUrl' in environment\n    ) {\n      // If a tenant's cognito configuration is provided in the\n      // \"environment\" object, then we take that instead of asking\n      // the visitor to provide the name of their tenant in order\n      // to do a look-up for that tenant's cognito configuration.\n      localStorage.setItem('tenantName', 'PooledTenants');\n      localStorage.setItem('userPoolId', (environment as any).userPoolId);\n      localStorage.setItem('appClientId', (environment as any).appClientId);\n      localStorage.setItem('apiGatewayUrl', (environment as any).apiGatewayUrl);\n      this.tenantNameRequired = false;\n    }\n  }\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    if (!this.tenantNameRequired) {\n      this.router.navigate(['/dashboard']);\n      return true;\n    }\n\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/orders/detail/{{ element.shardId }}:{{ element.orderId }}\">\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Edit Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Product ID</mat-label>\n          <input\n            matInput\n            value=\"{{ productId$ | async }}\"\n            [readonly]=\"true\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select formControlName=\"category\" required>\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{ category }}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router,\n    private route: ActivatedRoute\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      shardId: [],\n      productId: [],\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a class=\"link\" routerLink=\"/products/edit/{{ element.shardId }}:{{ element.productId }}\">\n              {{ element.name }}\n            </a>\n          </td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://ulp15c9bv2.execute-api.us-west-2.amazonaws.com/prod/',\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Solution/Lab5/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab5/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab5/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab5/scripts/deploy-updates.sh",
    "content": "#!/bin/bash\ncd ../server || exit # stop execution if cd fails\nrm -rf .aws-sam/\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n  \n#Deploying shared services changes\necho \"Deploying shared services changes\"  \necho Y | sam sync --stack-name serverless-saas -t shared-template.yaml --code --resource-id LambdaFunctions/CreateTenantAdminUserFunction --resource-id LambdaFunctions/ProvisionTenantFunction -u\n\ncd ../scripts || exit\n./geturl.sh"
  },
  {
    "path": "Solution/Lab5/scripts/deployment.sh",
    "content": "#!/bin/bash\n\nif [[ \"$#\" -eq 0 ]]; then\n  echo \"Invalid parameters\"\n  echo \"Command to deploy client code: deployment.sh -c\"\n  echo \"Command to deploy bootstrap server code: deployment.sh -b\"\n  echo \"Command to deploy CI/CD pipeline code: deployment.sh -p\"\n  echo \"Command to deploy CI/CD pipeline, bootstrap & tenant server code: deployment.sh -s\" \n  echo \"Command to deploy server & client code: deployment.sh -s -c\"\n  exit 1      \nfi\n\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        -s) server=1 ;;\n        -b) bootstrap=1 ;;        \n        -p) pipeline=1 ;;\n        -c) client=1 ;;\n        *) echo \"Unknown parameter passed: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSiteBucket'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\n\n\nif [[ $server -eq 1 ]] || [[ $pipeline -eq 1 ]]; then\n  echo \"CI/CD pipeline code is getting deployed\"\n  #Create CodeCommit repo\n  REGION=$(aws configure get region)\n  REPO=$(aws codecommit get-repository --repository-name aws-serverless-saas-workshop)\n  if [[ $? -ne 0 ]]; then\n      echo \"aws-serverless-saas-workshop codecommit repo is not present, will create one now\"\n      CREATE_REPO=$(aws codecommit create-repository --repository-name aws-serverless-saas-workshop --repository-description \"Serverless SaaS workshop repository\")\n      echo $CREATE_REPO\n      REPO_URL=\"codecommit::${REGION}://aws-serverless-saas-workshop\"\n      git remote add cc $REPO_URL\n      if [[ $? -ne 0 ]]; then\n           echo \"Setting url to remote cc\"\n           git remote set-url cc $REPO_URL\n      fi\n      git push --set-upstream cc main\n  fi\n\n  #Deploying CI/CD pipeline\n  cd ../server/TenantPipeline/\n  npm install && npm run build \n  cdk bootstrap  \n  cdk deploy --require-approval never\n\n  cd ../../scripts\n\nfi\n\nif [[ $server -eq 1 ]] || [[ $bootstrap -eq 1 ]]; then\n  echo \"Bootstrap server code is getting deployed\"\n  cd ../server\n  REGION=$(aws configure get region)\n  echo \"Validating server code using pylint\"\n  python3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\" -not -path \"./TenantPipeline/node_modules/*\")\n  if [[ $? -ne 0 ]]; then\n    echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n    exit 1\n  fi\n\n  sam build -t shared-template.yaml --use-container\n  \n  if [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\n  else\n    sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\n  fi\n    \n\n  cd ../scripts\n\nfi\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_BUCKET=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSiteBucket'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)  \nfi\n\n\n\nif [[ $client -eq 1 ]]; then\n  echo \"Client code is getting deployed\"\n  \n  ADMIN_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminApi'].OutputValue\" --output text)\n  \n  # Admin UI and Landing UI are configured in Lab2 \n  echo \"Admin UI and Landing UI are configured in Lab2. Only App UI will be reconfigured in this Lab5.\"\n  \n  # Configuring app UI \n\n  echo \"aws s3 ls s3://$APP_SITE_BUCKET\"\n  aws s3 ls s3://$APP_SITE_BUCKET \n  if [ $? -ne 0 ]; then\n      echo \"Error! S3 Bucket: $APP_SITE_BUCKET not readable\"\n      exit 1\n  fi\n\n  cd ../client/Application\n\n  echo \"Configuring environment for App Client\"\n\n  cat << EoF > ./src/environments/environment.prod.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n  };\nEoF\n  cat << EoF > ./src/environments/environment.ts\n  export const environment = {\n    production: true,\n    regApiGatewayUrl: '$ADMIN_APIGATEWAYURL'\n  };\nEoF\n\n  npm install --legacy-peer-deps && npm run build\n\n  echo \"aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET\"\n  aws s3 sync --delete --cache-control no-store dist s3://$APP_SITE_BUCKET \n\n  if [[ $? -ne 0 ]]; then\n      exit 1\n  fi\n\n  echo \"Completed configuring environment for App Client\"\n  echo \"Successfully completed redeploying Application UI\"\n\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\"\n  \n"
  },
  {
    "path": "Solution/Lab5/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Solution/Lab5/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Solution/Lab5/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Solution/Lab5/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab5/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\nimport metrics_manager\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n \n\ndef get_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n\n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    table = __get_dynamodb_table(event, dynamodb)\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" Determine the table name based upo pooled vs silo model\n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )        \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Solution/Lab5/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab5/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Solution/Lab5/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab5/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport random\nimport threading\nimport metrics_manager\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']    \n    table = __get_dynamodb_table(event, dynamodb)\n\n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):    \n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):    \n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )       \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n"
  },
  {
    "path": "Solution/Lab5/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab5/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml\n\n"
  },
  {
    "path": "Solution/Lab5/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Solution/Lab5/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\n\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']        \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n        \n\n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.SHARED_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab5/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user     \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']\n        apigateway_url = tenant_details['Item']['apiGatewayUrl']\n        \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    if (auth_manager.isSaaSProvider(user_role) == False):\n        if (isTenantAuthorizedForThisAPI(apigateway_url, api_gateway_arn_tmp[0]) == False):\n            logger.error('Unauthorized')\n            raise Exception('Unauthorized')\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.BUSINESS_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    \n    return authResponse\n\ndef isTenantAuthorizedForThisAPI(apigateway_url, current_api_id):\n    if(apigateway_url.split('.')[0] != 'https://' + current_api_id):\n        return False\n    else:\n        return True\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/events/env.json",
    "content": "{\n    \"CreateTenantAdminUserFunction\": {\n        \"DEFAULT_USER_POOL_ID\": \"us-west-2_uliP336sh\",  \n        \"LOG_LEVEL\": \"INFO\"\n    },\n    \"RegisterTenantFunction\": {\n        \"CREATE_TENANT_ADMIN_USER_FUNCTION\": \"arn:aws:lambda:us-west-2:779954754415:function:serverless-saas-admin-CreateTenantAdminUserFunctio-D5K1EGEMC4QG\",\n        \"CREATE_TENANT_FUNCTION\": \"arn:aws:lambda:us-west-2:779954754415:function:serverless-saas-admin-CreateTenantFunction-13GSSCVNKTV71\",\n        \"PREMIUM_TIER_API_KEY\": \"yy\",\n        \"STANDARD_TIER_API_KEY\": \"xx\",\n        \"BASIC_TIER_API_KEY\": \"zz\",\n        \"LOG_LEVEL\": \"DEBUG\"\n    }\n}"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/events/tenant-registration.json",
    "content": "\n{\n    \"body\": \"{\\\"tenantName\\\": \\\"First Tenant\\\", \\\"tenantAddress\\\": \\\"123 St\\\", \\\"tenantEmail\\\": \\\"a@a.com\\\", \\\"tenantPhone\\\": \\\"1234567890\\\", \\\"tenantTier\\\": \\\"Standard\\\",  \\\"tenantId\\\": \\\"62d1f8793b5e11eb876\\\", \\\"apiKey\\\": \\\"62d2062a3b5e11eb839457148f07121x\\\"}\"}"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/events/update_users_apikey_by_tenant.json",
    "content": "\n{\n    \"body\": \"{\\\"tenantId\\\": \\\"94d1bc6976ef11eb80cb81ceaed21f3f\\\", \\\"userPoolId\\\": \\\"us-west-2_vvN1hB5pd\\\", \\\"apiKey\\\": \\\"6db2bdc2-6d96-11eb-a56f-38f9d33cfd0f\\\"}\"\n}"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/events/user-management.json",
    "content": "\n{\n    \"tenantName\": \"First Tenant\", \n    \"tenantAddress\": \"123 St\", \n    \"tenantEmail\": \"a@a.com\", \n    \"tenantPhone\": \"1234567890\", \n    \"tenantTier\": \"Standard\", \n    \"dedicatedTenancy\": \"true\", \n    \"tenantId\": \"62d1f8793b5e11eb876\", \n    \"apiKey\": \"62d2062a3b5e11eb839457148f07121x\"\n}"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport metrics_manager\nimport auth_manager\nimport requests\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n\nregion = os.environ['AWS_REGION']\n\n#This method has been locked down to be only\ndef create_tenant(event, context):\n    api_gateway_url = ''       \n    tenant_details = json.loads(event['body'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    table_system_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n    try:          \n        # for pooled tenants the apigateway url is saving in settings during stack creation\n        # update from there during tenant creation\n        if(tenant_details['dedicatedTenancy'].lower()!= 'true'):\n            settings_response = table_system_settings.get_item(\n                Key={\n                    'settingName': 'apiGatewayUrl-Pooled'\n                } \n            )\n            api_gateway_url = settings_response['Item']['settingValue']\n\n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],\n                    'userPoolId': tenant_details['userPoolId'],                 \n                    'appClientId': tenant_details['appClientId'],\n                    'dedicatedTenancy': tenant_details['dedicatedTenancy'],\n                    'isActive': True,\n                    'apiGatewayUrl': api_gateway_url\n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n\n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n\n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier']\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    url_deprovision_tenant = os.environ['DEPROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            update_user_response = __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, url_deprovision_tenant)\n\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()    \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    url_provision_tenant = os.environ['PROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            provision_response = __invoke_provision_tenant(update_details, headers, auth, host, stage_name, url_provision_tenant)\n            logger.log_with_tenant_context(event, provision_response)\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()    \n\ndef load_tenant_config(event, context):\n    params = event['pathParameters']\n    tenantName = urllib.parse.unquote(params['tenantname'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    try:\n        response = table_tenant_details.query(\n            IndexName=\"ServerlessSaas-TenantConfig\",\n            KeyConditionExpression=Key('tenantName').eq(tenantName),\n            ProjectionExpression=\"userPoolId, appClientId, apiGatewayUrl\"\n        ) \n    except Exception as e:\n        raise Exception('Error getting tenant config', e)\n    else:\n        if (response['Count'] == 0):\n            return utils.create_notfound_response(\"Tenant not found.\"+\n            \"Please enter exact tenant name used during tenant registration.\")\n        else:\n            return utils.generate_response(response['Items'][0])        \n\ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url + update_details['tenantId']])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while deprovisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while deprovisioning tenant')\n        raise Exception('Error occured while deprovisioning tenant', e) \n    else:\n        return \"Success invoking deprovision tenant\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\ndef __invoke_provision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.post(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while provisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while provisioning tenant')\n        raise Exception('Error occured while provisioning tenant', e) \n    else:\n        return \"Success invoking provision tenant\"\n\ndef __getTenantManagementTable(event):\n    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n    dynamodb = boto3.resource('dynamodb', aws_access_key_id=accesskey, aws_secret_access_key=secretkey, aws_session_token=sessiontoken)\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    return table_tenant_details\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/tenant-provisioning.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport os\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\ntenant_stack_mapping_table_name = os.environ['TENANT_STACK_MAPPING_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ncodepipeline = boto3.client('codepipeline')\ncloudformation = boto3.client('cloudformation')\ntable_tenant_stack_mapping = dynamodb.Table(tenant_stack_mapping_table_name)\n\nstack_name = 'stack-{0}'\n@tracer.capture_lambda_handler\ndef provision_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'stackName': stack_name.format(tenant_details['tenantId']),\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_codepipeline = codepipeline.start_pipeline_execution(\n            name='serverless-saas-pipeline'\n        )\n\n        logger.info(response_ddb)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Provisioning Started\")\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef deprovision_tenant(event, context):\n    logger.info(\"Request received to deprovision a tenant\")\n    \n    tenantid_to_deprovision = event['tenantId']\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.delete_item(\n            Key={\n                    'tenantId': tenantid_to_deprovision                    \n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_cloudformation = cloudformation.delete_stack(\n            StackName=stack_name.format(tenantid_to_deprovision)\n        )\n\n        logger.info(response_cloudformation)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Deprovisioning Started\")\n\n \n"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\nprovision_tenant_resource_path = os.environ['PROVISION_TENANT_RESOURCE_PATH']\n\n\nlambda_client = boto3.client('lambda')\n\n\ndef register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n        tenant_details['dedicatedTenancy'] = 'false'\n\n        if (tenant_details['tenantTier'].upper() == utils.TenantTier.PLATINUM.value.upper()):\n            tenant_details['dedicatedTenancy'] = 'true'\n        \n        tenant_details['tenantId'] = tenant_id\n        \n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['userPoolId'] = create_user_response['message']['userPoolId']\n        tenant_details['appClientId'] = create_user_response['message']['appClientId']\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n        if (tenant_details['dedicatedTenancy'].upper() == 'TRUE'):\n            provision_tenant_response = __provision_tenant(tenant_details, headers, auth, host, stage_name)\n            logger.info(provision_tenant_response)\n\n        \n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\ndef __provision_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, provision_tenant_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()['message']\n    except Exception as e:\n        logger.error('Error occured while provisioning the tenant')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Solution/Lab5/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport utils\nimport metrics_manager\nimport auth_manager\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\ndef create_tenant_admin_user(event, context):\n    tenant_user_pool_id = os.environ['TENANT_USER_POOL_ID']\n    tenant_app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    \n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    if (tenant_details['dedicatedTenancy'] == 'true'):\n        user_pool_response = user_mgmt.create_user_pool(tenant_id)\n        user_pool_id = user_pool_response['UserPool']['Id']\n        logger.info (user_pool_id)\n        \n        app_client_response = user_mgmt.create_user_pool_client(user_pool_id)\n        logger.info(app_client_response)\n        app_client_id = app_client_response['UserPoolClient']['ClientId']\n        user_pool_domain_response = user_mgmt.create_user_pool_domain(user_pool_id, tenant_id)\n        \n        logger.info (\"New Tenant Created\")\n    else:\n        user_pool_id = tenant_user_pool_id\n        app_client_id = tenant_app_client_id\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']    \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user\")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                    \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n\n\n\n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']      \n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            logger.log_with_tenant_context(event, \"Request completed to get new user\")\n            return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")         \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)            \n            response = client.admin_update_user_attributes(\n                Username=user_name,\n                UserPoolId=user_pool_id,\n                UserAttributes=[\n                    {\n                        'Name': 'email',\n                        'Value': user_details['userEmail']\n                    },\n                    {\n                        'Name': 'custom:userRole',\n                        'Value': user_details['userRole'] \n                    }\n                ]\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to update user\")\n            return utils.create_success_response(\"user updated\")    \n\n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n            response = client.admin_disable_user(\n                    Username=user_name,\n                    UserPoolId=user_pool_id\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n            return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info\n\nclass UserManagement:\n    def create_user_pool(self, tenant_id):\n        application_site_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        email_message = ''.join([\"Login into tenant UI application at \", \n                        application_site_url,\n                        \" with username {username} and temporary password {####}\"])\n        email_subject = \"Your temporary password for tenant UI application\"  \n        response = client.create_user_pool(\n            PoolName= tenant_id + '-ServerlessSaaSUserPool',\n            AutoVerifiedAttributes=['email'],\n            AccountRecoverySetting={\n                'RecoveryMechanisms': [\n                    {\n                        'Priority': 1,\n                        'Name': 'verified_email'\n                    },\n                ]\n            },\n            Schema=[\n                {\n                    'Name': 'email',\n                    'AttributeDataType': 'String',\n                    'Required': True,                    \n                },\n                {\n                    'Name': 'tenantId',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                },            \n                {\n                    'Name': 'userRole',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                }\n            ],\n            AdminCreateUserConfig={\n                'InviteMessageTemplate': {\n                    'EmailMessage': email_message,\n                    'EmailSubject': email_subject\n                }\n            }\n        )    \n        return response\n\n    def create_user_pool_client(self, user_pool_id):\n        user_pool_callback_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        response = client.create_user_pool_client(\n            UserPoolId= user_pool_id,\n            ClientName= 'ServerlessSaaSClient',\n            GenerateSecret= False,\n            AllowedOAuthFlowsUserPoolClient= True,\n            AllowedOAuthFlows=[\n                'code', 'implicit'\n            ],\n            SupportedIdentityProviders=[\n                'COGNITO',\n            ],\n            CallbackURLs=[\n                user_pool_callback_url,\n            ],\n            LogoutURLs= [\n                user_pool_callback_url,\n            ],\n            AllowedOAuthScopes=[\n                'email',\n                'openid',\n                'profile'\n            ],\n            WriteAttributes=[\n                'email',\n                'custom:tenantId'\n            ]\n        )\n        return response\n\n    def create_user_pool_domain(self, user_pool_id, tenant_id):\n        response = client.create_user_pool_domain(\n            Domain= tenant_id + '-serverlesssaas',\n            UserPoolId=user_pool_id\n        )\n        return response\n\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/.gitignore",
    "content": "*.js\n!jest.config.js\n*.d.ts\nnode_modules\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n\n# Parcel default cache directory\n.parcel-cache\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/.npmignore",
    "content": "*.ts\n!*.d.ts\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/README.md",
    "content": "# Welcome to your CDK TypeScript project!\n\nThis is a blank project for TypeScript development with CDK.\n\nThe `cdk.json` file tells the CDK Toolkit how to execute your app.\n\n## Useful commands\n\n * `npm run build`   compile typescript to js\n * `npm run watch`   watch for changes and compile\n * `npm run test`    perform the jest unit tests\n * `cdk deploy`      deploy this stack to your default AWS account/region\n * `cdk diff`        compare deployed stack with current state\n * `cdk synth`       emits the synthesized CloudFormation template\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/bin/pipeline.ts",
    "content": "#!/usr/bin/env node\nimport 'source-map-support/register';\nimport * as cdk from 'aws-cdk-lib';\nimport { ServerlessSaaSStack } from '../lib/serverless-saas-stack';\n\nconst app = new cdk.App();\nnew ServerlessSaaSStack(app, 'serverless-saas-pipeline');\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/cdk.json",
    "content": "{\n  \"app\": \"npx ts-node bin/pipeline.ts\",\n  \"context\": {}\n}\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/jest.config.js",
    "content": "module.exports = {\n  roots: ['<rootDir>/test'],\n  testMatch: ['**/*.test.ts'],\n  transform: {\n    '^.+\\\\.tsx?$': 'ts-jest'\n  }\n};\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/lib/serverless-saas-stack.ts",
    "content": "// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: MIT-0\n\nimport { Construct } from 'constructs';\nimport * as cdk from 'aws-cdk-lib';\n\nimport * as s3 from 'aws-cdk-lib/aws-s3';\nimport * as codecommit from 'aws-cdk-lib/aws-codecommit';\nimport * as codepipeline from 'aws-cdk-lib/aws-codepipeline';\nimport * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';\nimport * as codebuild from 'aws-cdk-lib/aws-codebuild';\n\nimport { Function, Runtime, AssetCode } from 'aws-cdk-lib/aws-lambda';\nimport { PolicyStatement } from 'aws-cdk-lib/aws-iam';\nimport { Duration } from 'aws-cdk-lib';\n\n\nexport class ServerlessSaaSStack extends cdk.Stack {\n  constructor(scope: Construct, id: string, props?: cdk.StackProps) {\n    super(scope, id, props);\n\n    const artifactsBucket = new s3.Bucket(this, \"ArtifactsBucket\", {\n      encryption: s3.BucketEncryption.S3_MANAGED,\n    });\n\n    //Since this lambda is invoking cloudformation which is inturn deploying AWS resources, we are giving overly permissive permissions to this lambda. \n    //You can limit this based upon your use case and AWS Resources you need to deploy.\n    const lambdaPolicy = new PolicyStatement()\n        lambdaPolicy.addActions(\"*\")\n        lambdaPolicy.addResources(\"*\")\n\n    const lambdaFunction = new Function(this, \"deploy-tenant-stack\", {\n        handler: \"lambda-deploy-tenant-stack.lambda_handler\",\n        runtime: Runtime.PYTHON_3_9,\n        code: new AssetCode(`./resources`),\n        memorySize: 512,\n        timeout: Duration.seconds(10),\n        environment: {\n            BUCKET: artifactsBucket.bucketName,\n        },\n        initialPolicy: [lambdaPolicy],\n    })\n\n    // Pipeline creation starts\n    const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {\n      pipelineName: 'serverless-saas-pipeline',\n      artifactBucket: artifactsBucket\n    });\n\n    // Import existing CodeCommit sam-app repository\n    const codeRepo = codecommit.Repository.fromRepositoryName(\n      this,\n      'AppRepository', \n      'aws-serverless-saas-workshop'\n    );\n\n    // Declare source code as an artifact\n    const sourceOutput = new codepipeline.Artifact();\n\n    // Add source stage to pipeline\n    pipeline.addStage({\n      stageName: 'Source',\n      actions: [\n        new codepipeline_actions.CodeCommitSourceAction({\n          actionName: 'CodeCommit_Source',\n          repository: codeRepo,\n          branch: 'main',\n          output: sourceOutput,\n          variablesNamespace: 'SourceVariables'\n        }),\n      ],\n    });\n\n    // Declare build output as artifacts\n    const buildOutput = new codepipeline.Artifact();\n\n\n\n    //Declare a new CodeBuild project\n    const buildProject = new codebuild.PipelineProject(this, 'Build', {\n      buildSpec : codebuild.BuildSpec.fromSourceFilename(\"Lab5/server/tenant-buildspec.yml\"),\n      environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4 },\n      environmentVariables: {\n        'PACKAGE_BUCKET': {\n          value: artifactsBucket.bucketName\n        }\n      }\n    });\n\n    \n\n    // Add the build stage to our pipeline\n    pipeline.addStage({\n      stageName: 'Build',\n      actions: [\n        new codepipeline_actions.CodeBuildAction({\n          actionName: 'Build-Serverless-SaaS',\n          project: buildProject,\n          input: sourceOutput,\n          outputs: [buildOutput],\n        }),\n      ],\n    });\n\n    const deployOutput = new codepipeline.Artifact();\n\n\n    //Add the Lambda function that will deploy the tenant stack in a multitenant way\n    pipeline.addStage({\n      stageName: 'Deploy',\n      actions: [\n        new codepipeline_actions.LambdaInvokeAction({\n          actionName: 'DeployTenantStack',\n          lambda: lambdaFunction,\n          inputs: [buildOutput],\n          outputs: [deployOutput],\n          userParameters: {\n            'artifact': 'Artifact_Build_Build-Serverless-SaaS',\n            'template_file': 'packaged.yaml',\n            'commit_id': '#{SourceVariables.CommitId}'\n          }\n        }),\n      ],\n    });    \n  }\n}\n"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/package.json",
    "content": "\n{\n  \"name\": \"pipeline\",\n  \"version\": \"0.1.0\",\n  \"bin\": {\n    \"pipeline\": \"bin/pipeline.js\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc -w\",\n    \"test\": \"jest\",\n    \"cdk\": \"cdk\"\n  },\n  \"devDependencies\": {\n    \"@aws-cdk/assert\": \"1.64.1\",\n    \"@types/jest\": \"^26.0.10\",\n    \"@types/node\": \"10.17.27\",\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"node-notifier\": \"^8.0.1\",\n    \"ts-jest\": \"^26.2.0\",\n    \"ts-node\": \"^8.1.0\",\n    \"typescript\": \"4.9.5\",\n    \"@types/prettier\": \"2.6.0\"\n  },\n  \"dependencies\": {\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"source-map-support\": \"^0.5.19\"\n  }\n}"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/resources/lambda-deploy-tenant-stack.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom boto3.session import Session\n\nimport json\nimport boto3\nimport zipfile\nimport tempfile\nimport botocore\nimport traceback\nimport time\n\n\n\nprint('Loading function')\n\ncf = boto3.client('cloudformation')\ncode_pipeline = boto3.client('codepipeline')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_stack_mapping = dynamodb.Table('ServerlessSaaS-TenantStackMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\ntable_tenant_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n\ndef find_artifact(artifacts, name):\n    \"\"\"Finds the artifact 'name' among the 'artifacts'\n    \n    Args:\n        artifacts: The list of artifacts available to the function\n        name: The artifact we wish to use\n    Returns:\n        The artifact dictionary found\n    Raises:\n        Exception: If no matching artifact is found\n    \n    \"\"\"\n    for artifact in artifacts:\n        if artifact['name'] == name:\n            return artifact\n            \n    raise Exception('Input artifact named \"{0}\" not found in event'.format(name))\n\ndef get_template_url(s3, artifact, file_in_zip):\n    \"\"\"Gets the template artifact\n    \n    Downloads the artifact from the S3 artifact store to a temporary file\n    then extracts the zip and returns the file containing the CloudFormation\n    template.\n    \n    Args:\n        artifact: The artifact to download\n        file_in_zip: The path to the file within the zip containing the template\n        \n    Returns:\n        The CloudFormation template as a string\n        \n    Raises:\n        Exception: Any exception thrown while downloading the artifact or unzipping it\n    \n    \"\"\"\n    tmp_file = tempfile.NamedTemporaryFile()\n    bucket = artifact['location']['s3Location']['bucketName']\n    print(bucket)\n\n    key = artifact['location']['s3Location']['objectKey']\n    print(key)    \n    with tempfile.NamedTemporaryFile() as tmp_file:\n        s3.download_file(bucket, key, tmp_file.name)\n        with zipfile.ZipFile(tmp_file.name, 'r') as zip:\n            extracted_file = zip.extract(file_in_zip, '/tmp/')\n            s3.upload_file(extracted_file, bucket, file_in_zip)\n            template_url =''.join(['https://', bucket,'.s3.amazonaws.com/',file_in_zip])\n            return template_url  \n\n            \n   \ndef update_stack(stack, template_url, params):\n    \"\"\"Start a CloudFormation stack update\n    \n    Args:\n        stack: The stack to update\n        template_url: The template to apply\n        \n    Returns:\n        True if an update was started, false if there were no changes\n        to the template since the last update.\n        \n    Raises:\n        Exception: Any exception besides \"No updates are to be performed.\"\n    \n    \"\"\"\n    try:\n        cf.update_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n        return True\n        \n    except botocore.exceptions.ClientError as e:\n        if e.response['Error']['Message'] == 'No updates are to be performed.':\n            return False\n        else:\n            raise Exception('Error updating CloudFormation stack \"{0}\"'.format(stack), e)\n\ndef stack_exists(stack):\n    \"\"\"Check if a stack exists or not\n    \n    Args:\n        stack: The stack to check\n        \n    Returns:\n        True or False depending on whether the stack exists\n        \n    Raises:\n        Any exceptions raised .describe_stacks() besides that\n        the stack doesn't exist.\n        \n    \"\"\"\n    try:\n        cf.describe_stacks(StackName=stack)\n        return True\n    except botocore.exceptions.ClientError as e:\n        if \"does not exist\" in e.response['Error']['Message']:\n            return False\n        else:\n            raise e\n\ndef create_stack(stack, template_url, params):\n    \"\"\"Starts a new CloudFormation stack creation\n    \n    Args:\n        stack: The stack to be created\n        template_url: The template for the stack to be created with\n        \n    Throws:\n        Exception: Any exception thrown by .create_stack()\n    \"\"\"\n    cf.create_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n \ndef get_stack_status(stack):\n    \"\"\"Get the status of an existing CloudFormation stack\n    \n    Args:\n        stack: The name of the stack to check\n        \n    Returns:\n        The CloudFormation status string of the stack such as CREATE_COMPLETE\n        \n    Raises:\n        Exception: Any exception thrown by .describe_stacks()\n        \n    \"\"\"\n    stack_description = cf.describe_stacks(StackName=stack)\n    return stack_description['Stacks'][0]['StackStatus']\n  \ndef put_job_success(job, message):\n    \"\"\"Notify CodePipeline of a successful job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    print('Putting job success')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job)\n  \ndef put_job_failure(job, message):\n    \"\"\"Notify CodePipeline of a failed job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_failure_result()\n    \n    \"\"\"\n    print('Putting job failure')\n    print(message)\n    code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'})\n \ndef continue_job_later(job, message):\n    \"\"\"Notify CodePipeline of a continuing job\n    \n    This will cause CodePipeline to invoke the function again with the\n    supplied continuation token.\n    \n    Args:\n        job: The JobID\n        message: A message to be logged relating to the job status\n        continuation_token: The continuation token\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    \n    # Use the continuation token to keep track of any job execution state\n    # This data will be available when a new job is scheduled to continue the current execution\n    continuation_token = json.dumps({'previous_job_id': job})\n    \n    print('Putting job continuation')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job, continuationToken=continuation_token)\n\ndef start_update_or_create(job_id, stack, template_url, params):\n    \"\"\"Starts the stack update or create process\n    \n    If the stack exists then update, otherwise create.\n    \n    Args:\n        job_id: The ID of the CodePipeline job\n        stack: The stack to create or update\n        template_url: The template to create/update the stack with\n    \n    \"\"\"\n    if stack_exists(stack):\n        status = get_stack_status(stack)\n        if status not in ['CREATE_COMPLETE', 'ROLLBACK_COMPLETE', 'UPDATE_COMPLETE']:\n            # If the CloudFormation stack is not in a state where\n            # it can be updated again then fail the job right away.\n            put_job_failure(job_id, 'Stack cannot be updated when status is: ' + status)\n            return\n        \n        were_updates = update_stack(stack, template_url, params)\n        \n        if were_updates:\n            # If there were updates then continue the job so it can monitor\n            # the progress of the update.\n            continue_job_later(job_id, 'Stack update started')  \n            \n        else:\n            # If there were no updates then succeed the job immediately \n            put_job_success(job_id, 'There were no stack updates')    \n    else:\n        # If the stack doesn't already exist then create it instead\n        # of updating it.\n        create_stack(stack, template_url, params)\n        # Continue the job so the pipeline will wait for the CloudFormation\n        # stack to be created.\n        continue_job_later(job_id, 'Stack create started') \n\ndef check_stack_update_status(job_id, stack):\n    \"\"\"Monitor an already-running CloudFormation update/create\n    \n    Succeeds, fails or continues the job depending on the stack status.\n    \n    Args:\n        job_id: The CodePipeline job ID\n        stack: The stack to monitor\n    \n    \"\"\"\n    status = get_stack_status(stack)\n    if status in ['UPDATE_COMPLETE', 'CREATE_COMPLETE']:\n        # If the update/create finished successfully then\n        # succeed the job and don't continue.\n        put_job_success(job_id, 'Stack update complete')\n        \n    elif status in ['UPDATE_IN_PROGRESS', 'UPDATE_ROLLBACK_IN_PROGRESS', \n    'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', 'CREATE_IN_PROGRESS', \n    'ROLLBACK_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS']:\n        # If the job isn't finished yet then continue it\n        continue_job_later(job_id, 'Stack update still in progress') \n       \n    else:\n        # If the Stack is a state which isn't \"in progress\" or \"complete\"\n        # then the stack update/create has failed so end the job with\n        # a failed result.\n        put_job_failure(job_id, 'Update failed: ' + status)\n\ndef get_user_params(job_data):\n    \"\"\"Decodes the JSON user parameters and validates the required properties.\n    \n    Args:\n        job_data: The job data structure containing the UserParameters string which should be a valid JSON structure\n        \n    Returns:\n        The JSON parameters decoded as a dictionary.\n        \n    Raises:\n        Exception: The JSON can't be decoded or a property is missing.\n        \n    \"\"\"\n    try:\n        # Get the user parameters which contain the stack, artifact and file settings\n        user_parameters = job_data['actionConfiguration']['configuration']['UserParameters']\n        decoded_parameters = json.loads(user_parameters)\n            \n    except Exception:\n        # We're expecting the user parameters to be encoded as JSON\n        # so we can pass multiple values. If the JSON can't be decoded\n        # then fail the job with a helpful message.\n        raise Exception('UserParameters could not be decoded as JSON')\n    \n\n    if 'artifact' not in decoded_parameters:\n        # Validate that the artifact name is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the artifact name')\n    \n    if 'template_file' not in decoded_parameters:\n        # Validate that the template file is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the template file name')\n    \n    return decoded_parameters\n    \ndef setup_s3_client(job_data):\n    \"\"\"Creates an S3 client\n    \n    Uses the credentials passed in the event by CodePipeline. These\n    credentials can be used to access the artifact bucket.\n    \n    Args:\n        job_data: The job data structure\n        \n    Returns:\n        An S3 client with the appropriate credentials\n        \n    \"\"\"\n    # Could not use the artifact credentials to put object to artifacts s3 bucket.\n    # We are running into issue as described in https://github.com/aws/aws-cdk/issues/3274\n     \n    # key_id = job_data['artifactCredentials']['accessKeyId']\n    # key_secret = job_data['artifactCredentials']['secretAccessKey']\n    # session_token = job_data['artifactCredentials']['sessionToken']\n    \n    # session = Session(aws_access_key_id=key_id,\n    #     aws_secret_access_key=key_secret,\n    #     aws_session_token=session_token)\n    # return session.client('s3')\n    return boto3.client('s3')\n\ndef get_tenant_params(tenantId):\n    \"\"\"Get tenant details to be supplied to Cloud formation\n\n    Args:\n        tenantId (str): tenantId for which details are needed\n\n    Returns:\n        params from tenant management table\n    \"\"\"\n    params = []\n    param_tenantid = {}\n    param_tenantid['ParameterKey'] = 'TenantIdParameter'\n    param_tenantid['ParameterValue'] = tenantId\n    params.append(param_tenantid)\n\n    return params\n\ndef add_parameter(params, parameter_key, parameter_value):\n    parameter = {}\n    parameter['ParameterKey'] = parameter_key\n    parameter['ParameterValue'] = parameter_value\n    params.append(parameter)\n\n\n\n\ndef update_tenantstackmapping(tenantId, commit_id):\n    \"\"\"Update the tenant stack mapping table with the code pipeline job id\n\n    Args:\n        tenantId ([string]): tenant id for which data needs to be updated\n        job_id ([type]): current code pipeline job id\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    response = table_tenant_stack_mapping.update_item(\n            Key={'tenantId': tenantId},\n            UpdateExpression=\"set codeCommitId=:codeCommitId\",\n            ExpressionAttributeValues={\n            ':codeCommitId': commit_id\n            },\n            ReturnValues=\"NONE\") \n    \n    return response\n\ndef lambda_handler(event, context):\n    \"\"\"The Lambda function handler\n    \n    If a continuing job then checks the CloudFormation stack status\n    and updates the job accordingly.\n    \n    If a new job then kick of an update or creation of the target\n    CloudFormation stack.\n    \n    Args:\n        event: The event passed by Lambda\n        context: The context passed by Lambda\n        \n    \"\"\"\n    try:\n        # Extract the Job ID\n        job_id = event['CodePipeline.job']['id']\n        \n        # Extract the Job Data \n        job_data = event['CodePipeline.job']['data']\n        \n        # Extract the params\n        params = get_user_params(job_data)\n        \n        # Get the list of artifacts passed to the function\n        artifacts = job_data['inputArtifacts']\n        \n        artifact = params['artifact']\n        template_file = params['template_file']\n        commit_id = params['commit_id']\n\n        # Get all the stacks for each tenant to be updated/created from tenant stack mapping table\n        mappings = table_tenant_stack_mapping.scan()\n        print (mappings)\n        #Update/Create stacks for all tenants\n        for mapping in mappings['Items']:\n            stack = mapping['stackName']\n            tenantId = mapping['tenantId']\n            applyLatestRelease = mapping['applyLatestRelease']\n\n            if (applyLatestRelease):\n                # Get the parameters to be passed to the Cloudformation from tenant table\n                params = get_tenant_params(tenantId)\n                \n                if 'continuationToken' in job_data:\n                    # If we're continuing then the create/update has already been triggered\n                    # we just need to check if it has finished.\n                    check_stack_update_status(job_id, stack)\n                else:\n                    # Get the artifact details\n                    artifact_data = find_artifact(artifacts, artifact)\n                    # Get S3 client to access artifact with\n                    s3 = setup_s3_client(job_data)\n                    # Get the JSON template file out of the artifact\n                    template_url = get_template_url(s3, artifact_data, template_file)\n                    \n                    # Kick off a stack update or create\n                    start_update_or_create(job_id, stack, template_url, params)  \n\n                    # If we are applying the release, update tenant stack mapping with the pipe line id\n                    update_tenantstackmapping(tenantId, commit_id)\n    except Exception as e:\n        # If any other exceptions which we didn't expect are raised\n        # then fail the job and log the exception message.\n        print('Function failed due to exception.') \n        print(e)\n        traceback.print_exc()\n        put_job_failure(job_id, 'Function exception: ' + str(e))\n    \n    #put_job_success(job_id, \"Changeset executed successfully\")\n    print('Function complete.')   \n    return \"Complete.\""
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/test/pipeline.test.ts",
    "content": "// import { SynthUtils } from '@aws-cdk/assert';\n// import { Stack, App } from 'aws-cdk-lib';\n// import { Template } from 'aws-cdk-lib/assertions';\n// import * as Pipeline from '../lib/serverless-saas-stack';\n\n// test('synthesized cloudformation template should match original template', () => {\n//     const app = new App();\n//     const stack = new Pipeline.ServerlessSaaSStack(app, 'MyTestStack');\n//     const template = Template.fromStack(stack);\n//     expect(template).toMatchSnapshot();\n// });"
  },
  {
    "path": "Solution/Lab5/server/TenantPipeline/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"es2018\"],\n    \"declaration\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": false,\n    \"inlineSourceMap\": true,\n    \"inlineSources\": true,\n    \"experimentalDecorators\": true,\n    \"strictPropertyInitialization\": false,\n    \"typeRoots\": [\"./node_modules/@types\"]\n  },\n  \"exclude\": [\"cdk.out\"]\n}\n"
  },
  {
    "path": "Solution/Lab5/server/custom_resources/requirements.txt",
    "content": "requests\ncrhelper"
  },
  {
    "path": "Solution/Lab5/server/custom_resources/update_settings_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" Called as part of bootstrap template. \n        Inserts/Updates Settings table based upon the resources deployed inside bootstrap template\n        We use these settings inside tenant template\n\n    Args:\n            event ([type]): [description]\n            _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating settings\")\n\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    cognitoUserPoolId = event['ResourceProperties']['cognitoUserPoolId']\n    cognitoUserPoolClientId = event['ResourceProperties']['cognitoUserPoolClientId']\n\n    table_system_settings = dynamodb.Table(settings_table_name)\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'userPoolId-pooled',\n                    'settingValue' : cognitoUserPoolId\n                }\n            )\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'appClientId-pooled',\n                    'settingValue' : cognitoUserPoolClientId\n                }\n            )\n\n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab5/server/custom_resources/update_tenant_apigatewayurl.py",
    "content": "import json\nimport boto3\nimport logger\nfrom boto3.dynamodb.conditions import Key\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    client = boto3.client('dynamodb')\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" The URL for Tenant APIs(Product/Order) can differ by tenant.\n        For Pooled tenants it is shared and for Silo (Platinum tier tenants) it is unique to them.\n        This method keeps the URL for Pooled tenants inside Settings Table, since it is shared across multiple tenants,\n        And for Silo tenants inside the tenant management table along with other tenant settings, for that tenant\n\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Details table\")\n\n    tenant_details_table_name = event['ResourceProperties']['TenantDetailsTableName']\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    tenant_id = event['ResourceProperties']['TenantId']\n    tenant_api_gateway_url = event['ResourceProperties']['TenantApiGatewayUrl']\n\n\n    if(tenant_id.lower() =='pooled'):\n        # Note: Tenant management service will use below setting to update apiGatewayUrl for pooled tenants in TenantDetails table\n        settings_table = dynamodb.Table(settings_table_name)\n        settings_table.put_item(Item={\n                    'settingName': 'apiGatewayUrl-Pooled',\n                    'settingValue' : tenant_api_gateway_url                    \n                })\n        \n    else:\n        tenant_details = dynamodb.Table(tenant_details_table_name)\n        response = tenant_details.update_item(\n            Key={'tenantId': tenant_id},\n            UpdateExpression=\"set apiGatewayUrl=:apiGatewayUrl\",\n            ExpressionAttributeValues={\n            ':apiGatewayUrl': tenant_api_gateway_url\n            },\n            ReturnValues=\"NONE\") \n                   \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab5/server/custom_resources/update_tenantstackmap_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" One time entry for pooled tenants inside tenant stack mapping table.\n        This ensures that when code pipeline for tenant template is kicked off, it always create a default stack for pooled tenants.\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Stack Map\")\n\n    tenantstackmap_table_name = event['ResourceProperties']['TenantStackMappingTableName']\n    \n    table_stack_mapping = dynamodb.Table(tenantstackmap_table_name)\n    \n    response = table_stack_mapping.put_item(\n            Item={\n                    'tenantId': 'pooled',\n                    'stackName' : 'stack-pooled',\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )                  \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab5/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\ndef getPolicyForUser(user_role, service_identifier, tenant_id, region, aws_account_id):\n    \"\"\" This method is being used by Authorizer to get appropriate policy by user role\n\n    Args:\n        user_role (string): UserRoles enum\n        tenant_id (string): \n        region (string): \n        aws_account_id (string):  \n\n    Returns:\n        string: policy that tenant needs to assume\n    \"\"\"\n    iam_policy = \"\"\n    \n    if (isSystemAdmin(user_role)):\n        iam_policy = __getPolicyForSystemAdmin(region, aws_account_id)\n    elif (isTenantAdmin(user_role)):\n        iam_policy = __getPolicyForTenantAdmin(tenant_id, service_identifier, region, aws_account_id)\n    elif (isTenantUser(user_role)):\n        iam_policy = __getPolicyForTenantUser(tenant_id, region, aws_account_id)\n    \n    return iam_policy\n\ndef __getPolicyForSystemAdmin(region, aws_account_id):\n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                    \"dynamodb:UpdateItem\",\n                    \"dynamodb:GetItem\",\n                    \"dynamodb:PutItem\",\n                    \"dynamodb:DeleteItem\",\n                    \"dynamodb:Query\",          \n                    \"dynamodb:Scan\"\n                  ],\n                  \"Resource\": [\n                       \"arn:aws:dynamodb:{0}:{1}:table/*\".format(region, aws_account_id),\n                  ]\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n    \ndef __getPolicyForTenantAdmin(tenant_id, sevice_identifier, region, aws_account_id):\n    if (sevice_identifier == utils.Service_Identifier.SHARED_SERVICES.value):\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:Query\"                   \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantUserMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantDetails\".format(region, aws_account_id)\n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringEquals\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"               \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantStackMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-Settings\".format(region, aws_account_id)\n                    ]\n                }\n            ]\n        }\n    else:\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"    \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"       \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                }\n            ]\n        }\n    return json.dumps(policy)\n\ndef __getPolicyForTenantUser(tenant_id, region, aws_account_id):\n    \n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"      \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              },\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"               \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n\n"
  },
  {
    "path": "Solution/Lab5/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.error (log_message)\n\n\"\"\"Log with tenant context. Extracts tenant context from the lambda events\n\"\"\"\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Solution/Lab5/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Solution/Lab5/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth\npython-jose[cryptography]\naws_requests_auth"
  },
  {
    "path": "Solution/Lab5/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass TenantTier(Enum):\n    PLATINUM    = \"Platinum\"\n    PREMIUM     = \"Premium\"\n    STANDARD    = \"Standard\"\n    BASIC       = \"Basic\"\n\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \nclass Service_Identifier(Enum):\n    SHARED_SERVICES     = \"SharedServices\"\n    BUSINESS_SERVICES    = \"BusinessServices\"\n\ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef create_notfound_response(message):\n    return {\n        \"statusCode\": StatusCodes.NOT_FOUND.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth                   \n\ndef get_headers(event):\n    return event['headers']\n\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\n\n\n\n"
  },
  {
    "path": "Solution/Lab5/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]\n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/provisioning\"\n                   ]\n                  ] \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/provisioning/{tenantid}\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          /provisioning:\n            post:\n              summary: provisions resource for new tenant\n              description: provisions resource for new tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref ProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /provisioning/{tenantid}:\n            put:\n              summary: deprovision by tenant\n              description: deprovision by tenant\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:                \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /tenant/init/{tenantname}:\n            get:\n              summary: Returns a tenant config\n              description: Return a tenant config by a tenant name\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantConfigFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock        \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:                \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:            \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            api_key:\n              type: \"apiKey\"\n              name: \"x-api-key\"\n              in: \"header\"     \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n\nOutputs:\n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Solution/Lab5/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantConfigLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantConfigFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]    "
  },
  {
    "path": "Solution/Lab5/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\"\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"  \nResources:\n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]  \n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:\n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Solution/Lab5/server/nested_templates/custom_resources.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableName:\n    Type: String\n  TenantStackMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  UpdateSettingsTableFunctionArn:\n    Type: String\n  UpdateTenantStackMapTableFunctionArn:\n    Type: String\n  CognitoUserPoolId:\n    Type: String\n  CognitoUserPoolClientId:\n    Type: String\nResources:\n  #Custom resources\n  \n  UpdateSettingsTable:\n    Type: Custom::UpdateSettingsTable\n    Properties:\n      ServiceToken: !Ref UpdateSettingsTableFunctionArn\n      SettingsTableName: !Ref ServerlessSaaSSettingsTableName\n      cognitoUserPoolId: !Ref CognitoUserPoolId\n      cognitoUserPoolClientId: !Ref CognitoUserPoolClientId\n  \n  \n  UpdateTenantStackMap:\n    Type: Custom::UpdateTenantStackMap\n    Properties:\n      ServiceToken: !Ref UpdateTenantStackMapTableFunctionArn\n      TenantStackMappingTableName: !Ref TenantStackMappingTableName"
  },
  {
    "path": "Solution/Lab5/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String\n  TenantDetailsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  TenantStackMappingTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  #Tenant Authorizer\n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  AuthorizerAccessRole:\n    Type: AWS::IAM::Role\n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      RoleName: authorizer-access-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS:\n                - !GetAtt 'AuthorizerExecutionRole.Arn'\n            Action:\n              - sts:AssumeRole       \n      Policies:\n        - PolicyName: authorizer-access-role-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:BatchGetItem     \n                  - dynamodb:GetItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Query\n                  - dynamodb:Scan     \n                Resource:  \n                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*\n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerAccessRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n        \n          \n  \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          TENANT_USER_POOL_CALLBACK_URL: !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"   \n\n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"\n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n\n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n         \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n        \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n             \n  \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']] \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem                  \n                Resource:\n                  - !Ref ServerlessSaaSSettingsTableArn                 \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n          \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n          \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n                 \n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"\n                 \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n      \n  GetTenantConfigFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.load_tenant_config\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n                  \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n  #Tenant Provisioning\n  ProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-provisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-provisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem      \n                  - dynamodb:DeleteItem                  \n                Resource:\n                  - !Ref TenantStackMappingTableArn\n              - Effect: Allow\n                Action:\n                  - codepipeline:StartPipelineExecution\n                Resource:\n                  - !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:serverless-saas-pipeline\n              - Effect: Allow\n                Action:\n                  - cloudformation:DeleteStack\n                Resource: \"*\"                        \n  ProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.provision_tenant\n      Runtime: python3.9\n      Role: !GetAtt ProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n        \n  DeProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-deprovisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-deprovisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            #Since this lambda is invoking cloudformation which is inturn removing AWS resources, we are giving overly permissive permissions to this lambda. \n            #You can limit this based upon your use case and AWS Resources you need to remove.\n            Statement: \n              - Effect: Allow\n                Action: \"*\"                  \n                Resource: \"*\"                     \n  DeProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: DeProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.deprovision_tenant\n      Runtime: python3.9\n      Role: !GetAtt DeProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n         \n  UpdateSettingsTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-settingstable-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-settingstable-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref ServerlessSaaSSettingsTableArn\n  UpdateSettingsTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateSettingsTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_settings_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateSettingsTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantStackMapTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-tenantstackmap-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-tenantstackmap-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref TenantStackMappingTableArn\n  UpdateTenantStackMapTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantStackMapTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_tenantstackmap_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantStackMapTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers        \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ProvisionTenantFunctionArn: \n    Value: !GetAtt ProvisionTenantFunction.Arn\n  DeProvisionTenantFunctionArn: \n    Value: !GetAtt DeProvisionTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantConfigFunctionArn:\n    Value: !GetAtt GetTenantConfigFunction.Arn  \n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  AuthorizerExecutionRoleArn:\n    Value: !GetAtt AuthorizerExecutionRole.Arn      \n  UpdateSettingsTableFunctionArn:\n    Value: !GetAtt UpdateSettingsTableFunction.Arn    \n  UpdateTenantStackMapTableFunctionArn:\n    Value: !GetAtt UpdateTenantStackMapTableFunction.Arn\n  "
  },
  {
    "path": "Solution/Lab5/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  ServerlessSaaSSettingsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: settingName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: settingName\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-Settings\n  TenantStackMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantStackMapping\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE      \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL\nOutputs:\n  ServerlessSaaSSettingsTableArn: \n    Value: !GetAtt ServerlessSaaSSettingsTable.Arn\n  ServerlessSaaSSettingsTableName: \n    Value: !Ref ServerlessSaaSSettingsTable\n  TenantStackMappingTableArn: \n    Value: !GetAtt TenantStackMappingTable.Arn\n  TenantStackMappingTableName: \n    Value: !Ref TenantStackMappingTable\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Solution/Lab5/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n\n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'       \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName    \n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName   \n    Condition: IsNotRunningInEventEngine\n         "
  },
  {
    "path": "Solution/Lab5/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\""
  },
  {
    "path": "Solution/Lab5/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]   \nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter] \n        \n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn  \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n  #setup custom resources\n  CustomResources:\n    Type: AWS::Serverless::Application\n    DependsOn: APIs    \n    Properties:\n      Location: nested_templates/custom_resources.yaml\n      Parameters:\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn  \n        ServerlessSaaSSettingsTableName: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableName\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        UpdateSettingsTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateSettingsTableFunctionArn\n        UpdateTenantStackMapTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantStackMapTableFunctionArn\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId      \n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolId:\n    Description: The user pool id of Admin Management userpool \n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolId\"  \n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolClientId\"\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  \n  AuthorizerExecutionRoleArn:\n    Description: The Lambda authorizer execution role\n    Value: !GetAtt LambdaFunctions.Outputs.AuthorizerExecutionRoleArn \n    Export:\n      Name: \"Serverless-SaaS-AuthorizerExecutionRoleArn\"   "
  },
  {
    "path": "Solution/Lab5/server/tenant-buildspec.yml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nversion: 0.2\nphases:\n  install:    \n    runtime-versions:\n      python: 3.9\n    commands:\n      # Install packages or any pre-reqs in this phase.\n      # Upgrading SAM CLI to 1.33.0 version\n      - python -m pip install aws-sam-cli==1.33.0\n      - sam --version\n      # Installing project dependencies\n      - cd Lab5/server/ProductService\n      - python -m pip install -r requirements.txt \n      - cd ../OrderService\n      - python -m pip install -r requirements.txt \n      \n\n  pre_build:\n    commands:\n      # Run tests, lint scripts or any other pre-build checks.\n      - cd ..\n      - export PYTHONPATH=./ProductService/\n      # unit tests needs to be fixed. Commenting for now\n      #- python -m pytest tests/unit/ProductService-test_handler.py\n\n  build:\n    commands:\n      # Use Build phase to build your artifacts (compile, etc.)\n      - sam build -t tenant-template.yaml\n\n  post_build:\n    commands:\n      # Use Post-Build for notifications, git tags, upload artifacts to S3\n      - sam package --s3-bucket $PACKAGE_BUCKET --output-template-file packaged.yaml\n\nartifacts:\n  discard-paths: yes\n  files:\n    # List of local artifacts that will be passed down the pipeline\n    - Lab5/server/packaged.yaml"
  },
  {
    "path": "Solution/Lab5/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\n\n"
  },
  {
    "path": "Solution/Lab5/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  TenantIdParameter:\n    Type: String\n    Default: pooled\n    Description: Tenant ID for the stack\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\nConditions:\n  IsPooledDeploy: !Equals [ !Ref TenantIdParameter, pooled] \n  IsSiloDeploy: !Not [!Equals [ !Ref TenantIdParameter, pooled]]    \nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: !Join ['-', [serverless-saas-dependencies, !Ref TenantIdParameter]]\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Product, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Order, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  ProductFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, product-function-policy]]\n      Roles: \n        - !Ref ProductFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt ProductTable.Arn\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, product-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  OrderFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, order-function-policy]]\n      Roles: \n        - !Ref OrderFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt OrderTable.Arn\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, order-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n        \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !ImportValue Serverless-SaaS-AuthorizerExecutionRoleArn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolClientId\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Join ['-', [/aws/api-gateway/access-logs-serverless-saas-tenant-api-, !Ref TenantIdParameter]]\n      RetentionInDays: 30\n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join ['-', [!Ref TenantIdParameter, 'serverless-saas-tenant-api']]\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:        \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n  UpdateTenantApiGatewayUrlLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    DependsOn: ApiGatewayTenantApi\n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exec-role]]\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exe-policy ]]\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-Settings \n              - Effect: Allow\n                Action:\n                  - dynamodb:UpdateItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-TenantDetails    \n  UpdateTenantApiGatewayUrlFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantApiGatewayUrlLambdaExecutionRole\n    Properties:\n      CodeUri: custom_resources/\n      Handler: update_tenant_apigatewayurl.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantApiGatewayUrlLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantApiGatewayUrl:\n    Type: Custom::UpdateTenantApiGatewayUrl\n    DependsOn: UpdateTenantApiGatewayUrlFunction\n    Properties:\n      ServiceToken: !GetAtt UpdateTenantApiGatewayUrlFunction.Arn\n      TenantDetailsTableName: ServerlessSaaS-TenantDetails\n      SettingsTableName: ServerlessSaaS-Settings  \n      TenantId: !Ref TenantIdParameter  \n      TenantApiGatewayUrl: !Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/prod/\"    \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Solution/Lab6/client/Admin/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/README.md",
    "content": "# Admin\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"admin\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"4kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"admin:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"admin:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"admin:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/package.json",
    "content": "{\n  \"name\": \"admin\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/flex-layout\": \"~14.0.0-beta.40\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.1.3\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Tenants',\n    url: '/tenants',\n    icon: 'groups',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'dashboard',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'tenants',\n        loadChildren: () =>\n          import('./views/tenants/tenants.module').then((m) => m.TenantsModule),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <amplify-authenticator [hideSignUp]=\"true\">\n    <ng-template\n      amplifySlot=\"authenticated\"\n      let-user=\"user\"\n      let-signOut=\"signOut\"\n    >\n      <router-outlet></router-outlet>\n    </ng-template>\n  </amplify-authenticator>`,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { HttpInterceptorProviders } from './interceptors';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\n\n@NgModule({\n  declarations: [AppComponent, NavComponent, AuthComponent],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    HttpClientModule,\n    HttpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const HttpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/nav/nav.component.css",
    "content": ".sidenav-container {\n  height: 100%;\n}\n\n.sidenav-content-container {\n  background-color: lightgray;\n}\n\n.sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.mat-toolbar.mat-primary {\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.spinner-container {\n  height: 80%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/nav/nav.component.html",
    "content": "<mat-sidenav-container class=\"sidenav-container\">\n  <mat-sidenav\n    #drawer\n    class=\"sidenav\"\n    fixedInViewport\n    [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n    [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n    [opened]=\"(isHandset$ | async) === false\"\n  >\n    <mat-toolbar\n      ><mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon\n    ></mat-toolbar>\n    <mat-nav-list>\n      <mat-list-item *ngFor=\"let navItem of navItems\">\n        <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n          navItem.icon\n        }}</mat-icon>\n        <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n      </mat-list-item>\n    </mat-nav-list>\n  </mat-sidenav>\n  <mat-sidenav-content class=\"sidenav-content-container\">\n    <mat-toolbar>\n      <button\n        type=\"button\"\n        aria-label=\"Toggle sidenav\"\n        mat-icon-button\n        (click)=\"drawer.toggle()\"\n      >\n        <mat-icon aria-label=\"Side nav toggle icon\">menu</mat-icon>\n      </button>\n      <span class=\"spacer\"></span>\n      <button\n        mat-icon-button\n        aria-label=\"account circle with outlined person icon\"\n        [matMenuTriggerFor]=\"useroptions\"\n      >\n        <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n      </button>\n    </mat-toolbar>\n    <mat-menu #useroptions=\"matMenu\">\n      <ng-template #loggedOut>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n          <span>Login</span>\n        </button>\n      </ng-template>\n      <span style=\"margin: 8px\">{{ username$ | async }}</span>\n      <mat-divider></mat-divider>\n      <button mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n        <span>Profile</span>\n      </button>\n      <button routerLink=\"/auth/info\" mat-menu-item>\n        <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n        <span>Auth Debug</span>\n      </button>\n    </mat-menu>\n    <div class=\"spinner-container\" *ngIf=\"loading$ | async\">\n      <mat-spinner></mat-spinner>\n    </div>\n    <router-outlet></router-outlet>\n  </mat-sidenav-content>\n</mat-sidenav-container>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/nav/nav.component.spec.ts",
    "content": "import { LayoutModule } from '@angular/cdk/layout';\nimport { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\n\nimport { NavComponent } from './nav.component';\n\ndescribe('NavComponent', () => {\n  let component: NavComponent;\n  let fixture: ComponentFixture<NavComponent>;\n\n  beforeEach(waitForAsync(() => {\n    TestBed.configureTestingModule({\n      declarations: [NavComponent],\n      imports: [\n        NoopAnimationsModule,\n        LayoutModule,\n        MatButtonModule,\n        MatIconModule,\n        MatListModule,\n        MatSidenavModule,\n        MatToolbarModule,\n      ]\n    }).compileComponents();\n  }));\n\n  beforeEach(() => {\n    fixture = TestBed.createComponent(NavComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should compile', () => {\n    expect(component).toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.css'],\n})\nexport class NavComponent implements OnInit {\n  tenantName = '';\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router\n  ) {\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => err);\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map((sesh) => sesh && sesh.isValid())\n      );\n      const token$ = session$.pipe(map((sesh) => sesh && sesh.getIdToken()));\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/dashboard/dashboard.component.html",
    "content": "<!--/.row-->\n<mat-card class=\"card\">\n  <mat-card-header class=\"card-body\">\n    <div class=\"col-sm-5\">\n      <h4 class=\"card-title mb-0\">Traffic</h4>\n      <div class=\"small text-muted\">December 2020</div>\n    </div>\n  </mat-card-header>\n  <mat-card-content class=\"chart-wrapper\">\n    <canvas\n      baseChart\n      [type]=\"lineChartType\"\n      [datasets]=\"lineChartData\"\n      [labels]=\"lineChartLabels\"\n      [options]=\"lineChartOptions\"\n      [legend]=\"lineChartLegend\"\n    ></canvas>\n  </mat-card-content>\n</mat-card>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n\n.chart-wrapper {\n  padding-right: 20px;\n  position: relative;\n  margin: auto;\n  height: 80vh;\n  width: 80vw;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/dashboard/dashboard.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ChartConfiguration, ChartType } from 'chart.js';\nimport { TenantsService } from '../tenants/tenants.service';\n\ninterface DataSet {\n  label: string;\n  data: number[];\n}\ninterface ChartData {\n  tenantId: string;\n  dataSet: DataSet[];\n  totalOrders: number;\n}\n\n@Component({\n  templateUrl: 'dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n  selector: 'app-dashboard',\n})\nexport class DashboardComponent implements OnInit {\n  constructor(private tenantSvc: TenantsService) {}\n\n  data: ChartData[] = [];\n\n  // lineChart\n  public lineChartElements = 27;\n  public lineChartData1: Array<number> = [];\n  public lineChartData2: Array<number> = [];\n  public lineChartData3: Array<number> = [];\n\n  public lineChartData: Array<any> = [\n    {\n      data: this.lineChartData1,\n      label: 'Current',\n      backgroundColor: 'rgba(148,159,177,0.2)',\n      borderColor: 'rgba(148,159,177,1)',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData2,\n      label: 'Previous',\n      backgroundColor: 'rgba(77,83,96,0.2)',\n      borderColor: 'rgba(77,83,96,1)',\n      pointBackgroundColor: 'rgba(77,83,96,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(77,83,96,1)',\n      fill: 'origin',\n    },\n    {\n      data: this.lineChartData3,\n      label: 'BEP',\n      backgroundColor: 'rgba(255,0,0,0.3)',\n      borderColor: 'red',\n      pointBackgroundColor: 'rgba(148,159,177,1)',\n      pointBorderColor: '#fff',\n      pointHoverBackgroundColor: '#fff',\n      pointHoverBorderColor: 'rgba(148,159,177,0.8)',\n      fill: 'origin',\n    },\n  ];\n\n  public lineChartLegend = false;\n  /* tslint:disable:max-line-length */\n  public lineChartLabels: Array<any> = [\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Tuesday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n    'Monday',\n    'Thursday',\n    'Wednesday',\n    'Thursday',\n    'Friday',\n    'Saturday',\n    'Sunday',\n  ];\n  /* tslint:enable:max-line-length */\n\n  public lineChartType: ChartType = 'line';\n\n  public lineChartOptions: ChartConfiguration['options'] = {\n    maintainAspectRatio: false,\n    elements: {\n      line: {\n        tension: 0.5,\n      },\n    },\n    scales: {\n      // We use this empty structure as a placeholder for dynamic theming.\n      x: {},\n      'y-axis-0': {\n        position: 'left',\n      },\n      'y-axis-1': {\n        position: 'right',\n        grid: {\n          color: 'rgba(80,80,80,0.3)',\n        },\n        ticks: {\n          color: '#808080',\n        },\n      },\n    },\n  };\n\n  public random(min: number, max: number) {\n    return Math.floor(Math.random() * (max - min + 1) + min);\n  }\n\n  ngOnInit(): void {\n    for (let i = 0; i <= this.lineChartElements; i++) {\n      this.lineChartData1.push(this.random(50, 200));\n      this.lineChartData2.push(this.random(80, 100));\n      this.lineChartData3.push(65);\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/create/create.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision tenant</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!tenantForm.valid\"\n              type=\"submit\"\n              *ngIf=\"!submitting\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" [routerLink]=\"['/tenants']\" type=\"button\" *ngIf=\"!submitting\">\n              Cancel\n            </button>\n            <mat-card *ngIf=\"submitting\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n                >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { TenantsService } from '../tenants.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  submitting = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: [null, [Validators.required]],\n    tenantEmail: [null, [Validators.email, Validators.required]],\n    tenantTier: [null, [Validators.required]],\n    tenantPhone: [null],\n    tenantAddress: [null],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private tenantSvc: TenantsService,\n    private router: Router,\n    private _snackBar: MatSnackBar\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    const user = {\n      ...this.tenantForm.value,\n    };\n\n    this.tenantSvc.post(this.tenantForm.value).subscribe({\n      next: () => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('Successfully created new tenant!');\n        this.router.navigate(['tenants']);\n      },\n      error: (err) => {\n        this.submitting = false;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n        console.error(err);\n      },\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/list/list.component.html",
    "content": "<div class=\"tenant-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        Tenant List\n      </mat-card-title>\n      <div>\n        <table mat-table [dataSource]=\"tenantData\" class=\"mat-elevation-z1\">\n          <ng-container matColumnDef=\"tenantId\">\n            <th mat-header-cell *matHeaderCellDef>Id</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantId }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantName\">\n            <th mat-header-cell *matHeaderCellDef>Tenant Name</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantName }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantEmail\">\n            <th mat-header-cell *matHeaderCellDef>E-Mail</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantEmail }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"tenantTier\">\n            <th mat-header-cell *matHeaderCellDef>Plan</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.tenantTier }}</td>\n          </ng-container>\n\n          <ng-container matColumnDef=\"isActive\">\n            <th mat-header-cell *matHeaderCellDef>Status</th>\n            <td mat-cell *matCellDef=\"let element\">{{ element.isActive }}</td>\n          </ng-container>\n\n          <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n          <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n        </table>\n        <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n          <mat-progress-spinner\n            color=\"primary\"\n            mode=\"indeterminate\"\n            diameter=\"15\"\n          >\n          </mat-progress-spinner>\n        </mat-card>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/tenants/create\" mat-raised-button color=\"primary\">Add Tenant</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/list/list.component.scss",
    "content": ".tenant-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { Tenant } from '../models/tenant';\nimport { TenantsService } from '../tenants.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  tenants$ = new Observable<Tenant[]>();\n  tenantData: Tenant[] = [];\n  isLoading: boolean = true;\n  displayedColumns = [\n    'tenantId',\n    'tenantName',\n    'tenantEmail',\n    'tenantTier',\n    'isActive',\n  ];\n  constructor(private tenantSvc: TenantsService) {}\n\n  ngOnInit(): void {\n    this.tenantSvc.fetch().subscribe((data) => {\n      this.tenantData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/models/tenant.ts",
    "content": "export interface Tenant {\n  tenantId: string;\n  tenantName: string;\n  tenantEmail: string;\n  tenantTier: string;\n  isActive: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/tenants-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\n\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    component: ListComponent,\n    data: {\n      title: 'Tenant List',\n    },\n  },\n  {\n    path: 'create',\n    component: CreateComponent,\n    data: {\n      title: 'Provision new tenant',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class TenantsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/tenants.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ListComponent } from './list/list.component';\nimport { TenantsRoutingModule } from './tenants-routing.module';\nimport { CreateComponent } from './create/create.component';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    TenantsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class TenantsModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/tenants/tenants.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { environment } from 'src/environments/environment';\n\nimport { Tenant } from './models/tenant';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TenantsService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${environment.apiUrl}`;\n  tenantsApiUrl = `${this.baseUrl}/tenants`;\n  registrationApiUrl = `${this.baseUrl}/registration`;\n\n  // TODO strongly-type these anys as tenants once we dial in what the tenant call should return\n  fetch(): Observable<Tenant[]> {\n    return this.http.get<Tenant[]>(this.tenantsApiUrl);\n  }\n\n  post(tenant: Tenant): Observable<Tenant[]> {\n    return this.http.post<Tenant[]>(this.registrationApiUrl, tenant);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Tenant Id</mat-label>\n              <input\n                matInput\n                id=\"tenantid\"\n                name=\"tenantid\"\n                formControlName=\"tenantId\"\n                placeholder=\"Tenant Id\"\n                required\n              />\n              <mat-icon matSuffix>folder_special</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n      tenantId: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div>\n        <div class=\"row\">\n          <div class=\"col-md-12\">\n            <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n              <!-- Email Column -->\n              <ng-container matColumnDef=\"email\">\n                <th mat-header-cell *matHeaderCellDef>Email</th>\n                <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n              </ng-container>\n\n              <!-- Created Date Column -->\n              <ng-container matColumnDef=\"created\">\n                <th mat-header-cell *matHeaderCellDef>Created Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.created | date }}\n                </td>\n              </ng-container>\n\n              <!-- Modified Date Column -->\n              <ng-container matColumnDef=\"modified\">\n                <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.modified | date }}\n                </td>\n              </ng-container>\n\n              <!-- Status Column -->\n              <ng-container matColumnDef=\"status\">\n                <th mat-header-cell *matHeaderCellDef>Status</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.status }}\n                </td>\n              </ng-container>\n\n              <!-- Enabled Column -->\n              <ng-container matColumnDef=\"enabled\">\n                <th mat-header-cell *matHeaderCellDef>Enabled</th>\n                <td mat-cell *matCellDef=\"let element\">\n                  {{ element.enabled }}\n                </td>\n              </ng-container>\n              <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n              <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n            </table>\n            <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </mat-card>\n          </div>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.apiUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Admin/src/aws-exports.ts",
    "content": "const awsmobile = {\n  aws_project_region: 'us-west-2',\n  aws_cognito_region: 'us-west-2',\n  aws_user_pools_id: 'us-west-2_nWhwjijMc',\n  aws_user_pools_web_client_id: '61ipvhmvdrfso0cftpil9irk8k',\n};\n\nexport default awsmobile;\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Dashboard</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { Amplify } from 'aws-amplify';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\nimport aws_exports from './aws-exports';\nAmplify.configure(aws_exports);\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"styles/variables\";\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/functions\";\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Admin/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab6/client/Application/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab6/client/Application/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n#cypress\ncypress/videos/*\ncypress.env.json\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab6/client/Application/README.md",
    "content": "# Application\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab6/client/Application/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"application\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"2mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"6kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"application:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"application:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"application:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/cypress/README.md",
    "content": "# Application End-to-End Testing\n\n## Instructions\n\nTo run End-to-End (e2e) tests against the Sample Application, take the following steps:\n\n1. Make a copy of the example env file (`cypress.env.json.example`):\n\n```bash\ncp cypress.env.json.example cypress.env.json\n```\n\n2. Edit the new file and replace the sample values with real values. The following should help when deciding what to use in place of the sample values provided:\n\n- `host`: The URL where the Sample Application is running. If testing locally, this is usually `\"http://localhost:4200\"`.\n\n- `tenantName`: The name of the tenant used to identify the appropriate Cognito User Pool to use for Authentication.\n\n- `tenantUsername`: The username to use when logging in.\n\n- `tenantUserPassword`: The password to use when logging in.\n\n- `email`: The email address to use for testing. (This should be a valid email address.)\n\n3. Navigate to the root of the Application project (`aws-saas-factory-ref-solution-serverless-saas/clients/Application/`) and run the following:\n\n```bash\nnpx cypress run\n```\n\nThis will run the tests located in the `cypress/e2e` folder.\n\nThe documentation [here](https://docs.cypress.io/guides/guides/command-line#cypress-run) has more information on what can be passed in as arguments when running the Cypress tests.\n\nFor example, running the following will show the Cypress UI and what is happening as each of the tests are run:\n\n```bash\nnpx cypress run --headed\n```\n"
  },
  {
    "path": "Solution/Lab6/client/Application/cypress/e2e/1-getting-started/basic-access.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that the app redirects to /unauthorized when tenant is not set', () => {\n  it('redirects to unauthorized when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n  })\n\n  it('redirects to unauthorized when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n  })\n\n  it('redirects to unauthorized when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n  })\n})\n\ndescribe('check that the app redirects to a page with a sign-in form when tenant is set and user is not logged in', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').should('exist')\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.location('href').should('contain', '/dashboard')\n  })\n\n  it('redirects when visiting orders page', () => {\n    cy.visit(Cypress.env('host') + '/orders')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/orders')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting products page', () => {\n    cy.visit(Cypress.env('host') + '/products')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/products')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n\n  it('redirects when visiting a random page', () => {\n    cy.visit(Cypress.env('host') + '/random')\n    cy.location().should((loc) => {\n      expect(loc.href).to.not.contain('/unauthorized')\n      expect(loc.href).to.not.contain('/random')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[name=\"username\"]').should('exist');\n      cy.get('input[name=\"password\"]').should('exist');\n    })\n  })\n})\n"
  },
  {
    "path": "Solution/Lab6/client/Application/cypress/e2e/1-getting-started/product-testing.cy.js",
    "content": "/// <reference types=\"cypress\" />\n\ndescribe('check that product, order and user functionality works as expected', () => {\n  beforeEach(() => {\n    cy.visit(Cypress.env('host'))\n\n    cy.get('#tenantname').type(Cypress.env('tenantName'))\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/tenant/init/*',\n    }).as('getTenantInfo')\n\n    cy.contains('Submit').click()\n    cy.wait('@getTenantInfo')\n\n    cy.get('form input[name=\"username\"]').type(Cypress.env('tenantUsername'))\n    cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n    cy.get('form button[type=\"submit\"]').click()\n    cy.wait(1500)\n\n    cy.get('body').then(body => {\n      if (body.find('form input[name=\"confirm_password\"]').length > 0) {\n        cy.get('form input[name=\"password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form input[name=\"confirm_password\"]').type(Cypress.env('tenantUserPassword'))\n        cy.get('form button[type=\"submit\"]').click()\n      }\n    })\n  })\n\n  it('can create new users and display them', () => {\n    const email_username = Cypress.env('email').split('@')[0];\n    const email_domain = Cypress.env('email').split('@')[1];\n    const random_suffix = '+test'+Date.now().toString().slice(-3);\n    const myUser = {\n      name: \"myUser-\"+Date.now(),\n      email: email_username + random_suffix + '@' + email_domain,\n      role: 'userRole'+Date.now().toString().slice(-5)\n    }\n    cy.get(\"a\").contains(\"Users\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Add User\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"userName\"]').type(myUser.name)\n      cy.get('input[formcontrolname=\"userEmail\"]').type(myUser.email)\n      cy.get('input[formcontrolname=\"userRole\"]').type(myUser.role)\n    })\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/user',\n    }).as('postUser')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/users',\n    }).as('getUsers')\n    cy.get(\"button\").contains(\"Create\").click()\n    cy.wait('@postUser')\n\n    cy.go('back')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/users/list')\n    });\n\n    cy.wait('@getUsers')\n\n    cy.get('table td').contains(myUser.email)\n  })\n\n  it('can create a new order with a new product and see them listed', () => {\n    const myProduct = {\n      name: \"myProduct-\"+Date.now(),\n      price: Date.now().toString().slice(-3),\n      sku: Date.now().toString().slice(-5),\n      category: \"category3\",\n    }\n\n    const myOrder = {\n      name: \"myOrder-\"+Date.now(),\n    }\n\n    // NOW TESTING PRODUCT CREATION //\n    cy.get(\"a\").contains(\"Products\").click()\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.get(\"button[color='primary'\").contains(\"Create Product\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"name\"]').type(myProduct.name)\n      cy.get('input[formcontrolname=\"price\"]').type(myProduct.price)\n      cy.get('input[formcontrolname=\"sku\"]').type(myProduct.sku)\n      cy.get('mat-select[formcontrolname=\"category\"]').click()\n    })\n    cy.get('.mat-option-text').contains(myProduct.category).click()\n\n    cy.get(\"button\").contains(\"Submit\").should('not.be.disabled')\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/product',\n    }).as('postProduct')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/products',\n    }).as('getProducts')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postProduct')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/products/list')\n    });\n\n    cy.wait('@getProducts')\n\n    cy.get('table td').contains(myProduct.name)\n    cy.get('table td').contains(myProduct.price)\n\n    cy.get(\"a\").contains(\"Orders\").click()\n\n    // DONE TESTING PRODUCT CREATION //\n\n    // NOW TESTING ORDER CREATION //\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.get(\"button[color='primary']\").contains(\"Create Order\").click()\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/create')\n    });\n\n    cy.get('form').within(() => {\n      cy.get('input[formcontrolname=\"orderName\"]').type(myOrder.name)\n    })\n\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n    cy.get('div .row').contains(myProduct.name).parent().find(\"button[color='primary']\").click()\n\n    cy.intercept({\n      method: 'POST',\n      url: '**/order',\n    }).as('postOrder')\n\n    cy.intercept({\n      method: 'GET',\n      url: '**/orders',\n    }).as('getOrders')\n\n    cy.get(\"button\").contains(\"Submit\").click()\n    cy.wait('@postOrder')\n\n    cy.location().should((loc) => {\n      expect(loc.href).to.contain('/orders/list')\n    });\n\n    cy.wait('@getOrders')\n\n    cy.get('table td').contains(myOrder.name)\n    cy.get('table td').contains(new Intl.NumberFormat().format(myProduct.price * 2))\n\n    // DONE TESTING ORDER CREATION //\n  })\n})\n"
  },
  {
    "path": "Solution/Lab6/client/Application/cypress.config.ts",
    "content": "import { defineConfig } from 'cypress';\n\nexport default defineConfig({\n  e2e: {\n    supportFile: false,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here\n    },\n  },\n});\n"
  },
  {
    "path": "Solution/Lab6/client/Application/cypress.env.json.example",
    "content": "{\n  \"host\": \"http://localhost:4200\",\n  \"tenantName\": \"UPDATE_ME!\",\n  \"tenantUsername\": \"UPDATE_ME!\",\n  \"tenantUserPassword\": \"UPDATE_ME!\",\n  \"email\": \"test@example.com\"\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/application'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Application/package.json",
    "content": "{\n  \"name\": \"application\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\",\n    \"e2e\": \"npx cypress run\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"@aws-amplify/ui-angular\": \"~2.4.14\",\n    \"aws-amplify\": \"~4.3.27\",\n    \"bootstrap\": \"~5.2.0\",\n    \"chart.js\": \"~3.8.0\",\n    \"chartjs-plugin-datalabels\": \"~2.0.0\",\n    \"ng2-charts\": \"~4.0.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"cypress\": \"~10.3.1\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/_nav.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { INavData } from './models';\n\nexport const navItems: INavData[] = [\n  {\n    name: 'Dashboard',\n    url: '/dashboard',\n    icon: 'insights',\n  },\n  {\n    name: 'Products',\n    url: '/products',\n    icon: 'sell',\n  },\n  {\n    name: 'Orders',\n    url: '/orders',\n    icon: 'shopping_cart',\n  },\n  {\n    name: 'Users',\n    url: '/users',\n    icon: 'supervisor_account',\n  },\n];\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\nimport { CognitoGuard } from './cognito.guard';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'unauthorized',\n    pathMatch: 'full',\n  },\n  {\n    path: '',\n    component: NavComponent,\n    canActivate: [CognitoGuard],\n    data: {\n      title: 'Home',\n    },\n    children: [\n      {\n        path: 'auth/info',\n        component: AuthComponent,\n      },\n      {\n        path: 'dashboard',\n        loadChildren: () =>\n          import('./views/dashboard/dashboard.module').then(\n            (m) => m.DashboardModule\n          ),\n      },\n      {\n        path: 'orders',\n        loadChildren: () =>\n          import('./views/orders/orders.module').then((m) => m.OrdersModule),\n      },\n      {\n        path: 'products',\n        loadChildren: () =>\n          import('./views/products/products.module').then(\n            (m) => m.ProductsModule\n          ),\n      },\n      {\n        path: 'users',\n        loadChildren: () =>\n          import('./views/users/users.module').then((m) => m.UsersModule),\n      },\n    ],\n  },\n  {\n    path: 'unauthorized',\n    component: UnauthorizedComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'dashboard';\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { LayoutModule } from '@angular/cdk/layout';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\n\nimport { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatNativeDateModule } from '@angular/material/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { UnauthorizedComponent } from './views/error/unauthorized.component';\n\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { NavComponent } from './nav/nav.component';\nimport { AuthComponent } from './views/auth/auth.component';\nimport { httpInterceptorProviders } from './interceptors';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\n@NgModule({\n  declarations: [\n    AppComponent,\n    NavComponent,\n    AuthComponent,\n    UnauthorizedComponent,\n  ],\n  imports: [\n    AmplifyAuthenticatorModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    LayoutModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    MatNativeDateModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    MatToolbarModule,\n    MatSnackBarModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n    httpInterceptorProviders,\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/cognito.guard.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  ActivatedRouteSnapshot,\n  CanActivate,\n  Router,\n  RouterStateSnapshot,\n} from '@angular/router';\nimport { Auth } from 'aws-amplify';\nimport { AuthConfigurationService } from './views/auth/auth-configuration.service';\n\n@Injectable({ providedIn: 'root' })\nexport class CognitoGuard implements CanActivate {\n  constructor(\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {}\n\n  canActivate(\n    route: ActivatedRouteSnapshot,\n    state: RouterStateSnapshot\n  ): Promise<boolean> {\n    if (!this.authConfigService.configureAmplifyAuth()) {\n      this.authConfigService.cleanLocalStorage();\n      this.router.navigate(['/unauthorized']);\n      return new Promise<boolean>((res, rej) => {\n        res(false);\n      });\n    }\n\n    return Auth.currentSession()\n      .then((u) => {\n        if (u.isValid()) {\n          return true;\n        } else {\n          this.authConfigService.cleanLocalStorage();\n          this.router.navigate(['/unauthorized']);\n          return false;\n        }\n      })\n      .catch((e) => {\n        if (state.url === '/dashboard') {\n          // if we're going to the dashboard and we're not logged in,\n          // don't stop the flow as the amplify-authenticator will\n          // route requests going to the dashboard to the sign-in page.\n          return true;\n        }\n\n        console.log('Error getting current session', e);\n        this.router.navigate(['/unauthorized']);\n        return false;\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/interceptors/auth.interceptor.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Injectable } from '@angular/core';\nimport {\n  HttpRequest,\n  HttpHandler,\n  HttpEvent,\n  HttpInterceptor,\n} from '@angular/common/http';\nimport { from, Observable } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { filter, map, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class AuthInterceptor implements HttpInterceptor {\n  constructor() {}\n  idToken = '';\n\n  intercept(\n    req: HttpRequest<any>,\n    next: HttpHandler\n  ): Observable<HttpEvent<any>> {\n    if (req.url.includes('tenant/init')) {\n      return next.handle(req);\n    }\n\n    const s = Auth.currentSession().catch((err) => console.log(err));\n    const session$ = from(s);\n\n    return session$.pipe(\n      filter((sesh) => !!sesh),\n      map((sesh) => (!!sesh ? sesh.getIdToken().getJwtToken() : '')),\n      switchMap((tok) => {\n        req = req.clone({\n          headers: req.headers.set('Authorization', 'Bearer ' + tok),\n        });\n        return next.handle(req);\n      })\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/interceptors/index.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HTTP_INTERCEPTORS } from '@angular/common/http';\n\nimport { AuthInterceptor } from './auth.interceptor';\n\n/** Http interceptor providers in outside-in order */\nexport const httpInterceptorProviders = [\n  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },\n];\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/models/index.ts",
    "content": "export * from './interfaces';\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/models/interfaces.ts",
    "content": "export interface INavData {\n  name?: string;\n  url?: string | any[];\n  href?: string;\n  icon?: string;\n  title?: boolean;\n  children?: INavData[];\n  variant?: string;\n  divider?: boolean;\n  class?: string;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/nav/nav.component.html",
    "content": "<amplify-authenticator [hideSignUp]=\"true\">\n  <ng-template\n    amplifySlot=\"authenticated\"\n    let-user=\"user\"\n  >\n    <mat-sidenav-container fullscreen>\n      <mat-sidenav\n        [mode]=\"'side'\"\n        #drawer\n        class=\"sidenav\"\n        fixedInViewport\n        [attr.role]=\"(isHandset$ | async) ? 'dialog' : 'navigation'\"\n        [mode]=\"(isHandset$ | async) ? 'over' : 'side'\"\n        [opened]=\"(isHandset$ | async) === false\"\n      >\n        <div class=\"sidebar-icon-container\">\n          <mat-icon svgIcon=\"saas-commerce\" class=\"logo\"></mat-icon>\n        </div>\n        <mat-divider></mat-divider>\n        <mat-nav-list>\n          <mat-list-item *ngFor=\"let navItem of navItems\">\n            <mat-icon mat-list-icon class=\"nav-icon material-symbols-outlined\">{{\n              navItem.icon\n            }}</mat-icon>\n            <a mat-list-item routerLink=\"{{ navItem.url }}\">{{ navItem.name }}</a>\n          </mat-list-item>\n        </mat-nav-list>\n      </mat-sidenav>\n\n      <mat-toolbar #toolbar class=\"sidenav-content-container\">\n        <button\n          type=\"button\"\n          mat-icon-button\n          (click)=\"drawer.toggle()\"\n          title=\"Open sidenav\"\n        >\n          <mat-icon>menu</mat-icon>\n        </button>\n        <span>{{ (companyName$ | async) || \"\" }}</span>\n        <span class=\"spacer\"></span>\n        <button\n          mat-icon-button\n          aria-label=\"account circle with outlined person icon\"\n          [matMenuTriggerFor]=\"useroptions\"\n        >\n          <mat-icon class=\"material-symbols-outlined\">person_filled</mat-icon>\n        </button>\n      </mat-toolbar>\n      <mat-menu #useroptions=\"matMenu\">\n        <ng-template #loggedOut>\n          <button mat-menu-item>\n            <mat-icon class=\"material-symbols-outlined\">lock_open</mat-icon>\n            <span>Login</span>\n          </button>\n        </ng-template>\n        <span style=\"margin: 8px\">{{ (username$ | async) || user.username}}</span>\n        <mat-divider></mat-divider>\n        <button mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">face</mat-icon>\n          <span>Profile</span>\n        </button>\n        <button routerLink=\"/auth/info\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">construction</mat-icon>\n          <span>Auth Debug</span>\n        </button>\n        <button (click)=\"logout()\" mat-menu-item>\n          <mat-icon class=\"material-symbols-outlined\">logout</mat-icon>\n          <span>Sign Out</span>\n        </button>\n      </mat-menu>\n      <div class=\"content\" #main>\n        <router-outlet></router-outlet>\n      </div>\n\n      <div class=\"footer\" #footer>\n        <div class=\"footer-text\">\n          <span>Serverless SaaS Workshop &copy; 2022 AWS</span>\n          <span class=\"spacer\"></span>\n          <span>\n            Powered by <a href=\"https://aws.amazon.com\">SaaS Factory</a>\n          </span>\n        </div>\n      </div>\n    </mat-sidenav-container>\n  </ng-template>\n</amplify-authenticator>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/nav/nav.component.scss",
    "content": ".sidenav {\n  width: 200px;\n  background-color: #2f353a;\n}\n\n.mat-list-item {\n  color: whitesmoke;\n}\n\n.sidebar-icon-container {\n  height: 64px;\n  background-color: whitesmoke;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.spacer {\n  flex: 1 1 auto;\n}\n\n.material-symbols-outlined {\n  font-variation-settings: \"FILL\" 0, \"wght\" 400, \"GRAD\" 0, \"opsz\" 48;\n}\n\n.logo {\n  width: 100px;\n  height: auto;\n}\n\n.nav-icon {\n  color: #20a8d8;\n}\n\n.footer {\n  position: fixed;\n  bottom: 0;\n  width: 100%;\n  height: 40px;\n  background-color: whitesmoke;\n  color: #2d3337;\n  // text-align: center;\n  margin: 0px;\n}\n\n.footer-text {\n  display: flex;\n}\n\n.content {\n  position: absolute;\n  width: 100%;\n  height: 90%;\n  max-height: 90%;\n  overflow: auto;\n  background-color: lightgray;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/nav/nav.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { from, Observable, of } from 'rxjs';\nimport { filter, map, shareReplay } from 'rxjs/operators';\nimport {\n  NavigationCancel,\n  NavigationEnd,\n  NavigationError,\n  NavigationStart,\n  Router,\n} from '@angular/router';\n\nimport { AuthenticatorService } from '@aws-amplify/ui-angular';\nimport { Auth } from 'aws-amplify';\nimport { navItems } from '../_nav';\nimport { AuthConfigurationService } from './../views/auth/auth-configuration.service';\n\n@Component({\n  selector: 'app-nav',\n  templateUrl: './nav.component.html',\n  styleUrls: ['./nav.component.scss'],\n})\nexport class NavComponent implements OnInit {\n  loading$: Observable<boolean> = of(false);\n  isAuthenticated$: Observable<Boolean> | undefined;\n  username$: Observable<string> | undefined;\n  companyName$: Observable<string> | undefined;\n  public navItems = navItems;\n  isHandset$: Observable<boolean> = this.breakpointObserver\n    .observe(Breakpoints.Handset)\n    .pipe(\n      map((result) => result.matches),\n      shareReplay()\n    );\n\n  constructor(\n    private breakpointObserver: BreakpointObserver,\n    private router: Router,\n    private authConfigService: AuthConfigurationService\n  ) {\n    // this.configSvc.loadConfigurations().subscribe((val) => console.log(val));\n    this.loading$ = this.router.events.pipe(\n      filter(\n        (e) =>\n          e instanceof NavigationStart ||\n          e instanceof NavigationEnd ||\n          e instanceof NavigationCancel ||\n          e instanceof NavigationError\n      ),\n      map((e) => e instanceof NavigationStart)\n    );\n  }\n\n  ngOnInit(): void {\n    try {\n      const s = Auth.currentSession().catch((err) => {\n        console.log('Failed to get current session. Err: ', err);\n        return err;\n      });\n      const session$ = from(s);\n      this.isAuthenticated$ = session$.pipe(\n        filter((sesh) => !!sesh),\n        map(\n          (sesh) => sesh && typeof sesh.isValid === 'function' && sesh.isValid()\n        )\n      );\n\n      const token$ = session$.pipe(\n        map(\n          (sesh) =>\n            sesh && typeof sesh.getIdToken === 'function' && sesh.getIdToken()\n        )\n      );\n      this.username$ = token$.pipe(\n        map((t) => t && t.payload && t.payload['cognito:username'])\n      );\n      this.companyName$ = token$.pipe(\n        map((t) => t.payload && t.payload['custom:company-name'])\n      );\n    } catch (err) {\n      console.error('Unable to get current session.');\n    }\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true })\n      .then((e) => {\n        this.authConfigService.cleanLocalStorage();\n        this.router.navigate(['/unauthorized']);\n      })\n      .catch((err) => {\n        console.error('Error logging out: ', err);\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/auth/auth-configuration.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport {\n  HttpClient,\n  HttpParams,\n  HttpParamsOptions,\n} from '@angular/common/http';\nimport { Injectable, OnInit } from '@angular/core';\nimport { map, switchMap, catchError } from 'rxjs/operators';\nimport { throwError } from 'rxjs';\nimport { environment } from '../../../environments/environment';\nimport { ConfigParams } from './models/config-params';\nimport { ActivatedRoute } from '@angular/router';\nimport Amplify from 'aws-amplify';\nimport { Auth } from 'aws-amplify';\nimport { Router } from '@angular/router';\nimport { from, Observable } from 'rxjs';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class AuthConfigurationService {\n  params$: Observable<ConfigParams>;\n  params: ConfigParams;\n  tenantName: string;\n\n  constructor(\n    private http: HttpClient,\n    private route: ActivatedRoute,\n    private router: Router\n  ) {}\n\n  public setTenantConfig(tenantName: string): Promise<any> {\n    const url = `${environment.regApiGatewayUrl}/tenant/init/` + tenantName;\n    this.params$ = this.http.get<ConfigParams>(url);\n    const setup$ = this.params$.pipe(\n      map((val) => {\n        // remove trailing slash (/) if present\n        val.apiGatewayUrl = val.apiGatewayUrl.replace(/\\/$/, '');\n        localStorage.setItem('userPoolId', val.userPoolId);\n        localStorage.setItem('tenantName', tenantName);\n        localStorage.setItem('appClientId', val.appClientId);\n        localStorage.setItem('apiGatewayUrl', val.apiGatewayUrl);\n        return 'success';\n      }),\n      catchError((error) => {\n        console.log('Error setting tenant config: ', error);\n        return throwError(error);\n      })\n    );\n\n    return setup$.toPromise();\n  }\n\n  configureAmplifyAuth(): boolean {\n    try {\n      const userPoolId = localStorage.getItem('userPoolId');\n      const appClientId = localStorage.getItem('appClientId');\n\n      if (!userPoolId || !appClientId) {\n        return false;\n      }\n      const region = userPoolId?.split('_')[0];\n      const awsmobile = {\n        aws_project_region: region,\n        aws_cognito_region: region,\n        aws_user_pools_id: userPoolId,\n        aws_user_pools_web_client_id: appClientId,\n      };\n\n      Amplify.configure(awsmobile);\n      return true;\n    } catch (err) {\n      console.error('Unable to initialize amplify auth.', err);\n      return false;\n    }\n  }\n\n  cleanLocalStorage() {\n    localStorage.removeItem('tenantName');\n    localStorage.removeItem('userPoolId');\n    localStorage.removeItem('appClientId');\n    localStorage.removeItem('apiGatewayUrl');\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/auth/auth.component.html",
    "content": "<div *ngIf=\"isAuthenticated$ | async as isAuthenticated\">\n  <div class=\"animated fadeIn\">\n    <div class=\"row\">\n      <div class=\"col-md-6\">\n        <button type=\"button\" class=\"btn btn-primary\" (click)=\"logout()\">\n          Logout\n        </button>\n      </div>\n    </div>\n    <div class=\"row mt-4\">\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">Access Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{accessToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n      <div class=\"col-sm-6\">\n        <mat-card class=\"card\">\n          <mat-card-header class=\"card-header\">ID Token</mat-card-header>\n          <div class=\"card-body\">\n            <pre>\n              <code spellcheck=\"false\">\n                {{idToken$ | async}}\n              </code>\n            </pre>\n          </div>\n        </mat-card>\n      </div>\n    </div>\n  </div>\n\n  <hr />\n\n  <br />\n  <br />\n  <mat-card class=\"card\">\n    <mat-card-header><h2>User Data</h2></mat-card-header>\n    Is Authenticated: {{ isAuthenticated$ | async }}\n    <pre>{{ session$ | async | json }}</pre>\n  </mat-card>\n  <br />\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/auth/auth.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\npre,\ncode {\n  font-family: monospace, monospace;\n}\npre {\n  white-space: pre-wrap; /* css-3 */\n  white-space: -moz-pre-wrap; /* Mozilla, since 1999 */\n  white-space: -pre-wrap; /* Opera 4-6 */\n  white-space: -o-pre-wrap; /* Opera 7 */\n  word-wrap: break-word; /* Internet Explorer 5.5+ */\n  overflow: auto;\n}\n\n.card {\n  margin: 20px;\n}\n\n.card-header {\n  justify-content: center;\n  font-size: larger;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/auth/auth.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { from, Observable, pipe } from 'rxjs';\nimport { Auth } from 'aws-amplify';\nimport { CognitoUserSession } from 'amazon-cognito-identity-js';\nimport { map } from 'rxjs/operators';\n\n@Component({\n  templateUrl: './auth.component.html',\n  styleUrls: ['./auth.component.scss'],\n})\nexport class AuthComponent implements OnInit {\n  session$: Observable<CognitoUserSession> | undefined;\n  userData$: Observable<any> | undefined;\n  isAuthenticated$: Observable<boolean> | undefined;\n  checkSessionChanged$: Observable<boolean> | undefined;\n  idToken$: Observable<string> | undefined;\n  accessToken$: Observable<string> | undefined;\n  checkSessionChanged: any;\n\n  constructor() {}\n\n  ngOnInit(): void {\n    this.session$ = from(Auth.currentSession());\n    this.accessToken$ = this.session$.pipe(\n      map((sesh) => sesh.getAccessToken().getJwtToken())\n    );\n    this.idToken$ = this.session$.pipe(\n      map((sesh) => sesh.getIdToken().getJwtToken())\n    );\n    this.isAuthenticated$ = this.session$.pipe(map((sesh) => sesh.isValid()));\n  }\n\n  async logout() {\n    await Auth.signOut({ global: true });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/auth/models/config-params.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nexport interface ConfigParams {\n  appClientId: string;\n  userPoolId: string;\n  apiGatewayUrl: string;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/dashboard/dashboard-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\n\nimport { DashboardComponent } from './dashboard.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    component: DashboardComponent,\n    data: {\n      title: 'Dashboard',\n    },\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class DashboardRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/dashboard/dashboard.component.html",
    "content": "<div class=\"grid-container\">\n  <h1 class=\"mat-h1\">Dashboard</h1>\n  <mat-grid-list cols=\"2\" rowHeight=\"350px\">\n    <mat-grid-tile\n      *ngFor=\"let card of cards | async\"\n      [colspan]=\"card.cols\"\n      [rowspan]=\"card.rows\"\n    >\n      <mat-card class=\"dashboard-card\">\n        <mat-card-header>\n          <mat-card-title>\n            {{ card.title }}\n            <button\n              mat-icon-button\n              class=\"more-button\"\n              [matMenuTriggerFor]=\"menu\"\n              aria-label=\"Toggle menu\"\n            >\n              <mat-icon>more_vert</mat-icon>\n            </button>\n            <mat-menu #menu=\"matMenu\" xPosition=\"before\">\n              <button mat-menu-item>Expand</button>\n              <button mat-menu-item>Remove</button>\n            </mat-menu>\n          </mat-card-title>\n        </mat-card-header>\n        <mat-card-content>\n          <div class=\"chart-wrapper chart-container\">\n            <canvas\n              baseChart\n              [data]=\"barChartData\"\n              [options]=\"barChartOptions\"\n              [plugins]=\"barChartPlugins\"\n              [type]=\"barChartType\"\n            >\n            </canvas>\n          </div>\n        </mat-card-content>\n      </mat-card>\n    </mat-grid-tile>\n  </mat-grid-list>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/dashboard/dashboard.component.scss",
    "content": ".grid-container {\n  margin: 20px;\n}\n\n.dashboard-card {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  bottom: 15px;\n}\n\n.more-button {\n  position: absolute;\n  top: 5px;\n  right: 10px;\n  border: none;\n}\n\n.dashboard-card-content {\n  text-align: center;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/dashboard/dashboard.component.ts",
    "content": "import { Component, ViewChild } from '@angular/core';\nimport { map } from 'rxjs/operators';\nimport { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';\nimport { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';\nimport { BaseChartDirective } from 'ng2-charts';\n\nimport DataLabelsPlugin from 'chartjs-plugin-datalabels';\n@Component({\n  selector: 'app-dashboard',\n  templateUrl: './dashboard.component.html',\n  styleUrls: ['./dashboard.component.scss'],\n})\nexport class DashboardComponent {\n  @ViewChild(BaseChartDirective) chart: BaseChartDirective | undefined;\n\n  public barChartOptions: ChartConfiguration['options'] = {\n    responsive: true,\n    maintainAspectRatio: false,\n    // We use these empty structures as placeholders for dynamic theming.\n    scales: {\n      x: {},\n      y: {\n        min: 10,\n      },\n    },\n    plugins: {\n      legend: {\n        display: true,\n      },\n      datalabels: {\n        anchor: 'end',\n        align: 'end',\n      },\n    },\n  };\n  public barChartType: ChartType = 'bar';\n  public barChartPlugins = [DataLabelsPlugin];\n\n  public barChartData: ChartData<'bar'> = {\n    labels: ['2006', '2007', '2008', '2009', '2010', '2011', '2012'],\n    datasets: [\n      { data: [65, 59, 80, 81, 56, 55, 40], label: 'Series A' },\n      { data: [28, 48, 40, 19, 86, 27, 90], label: 'Series B' },\n    ],\n  };\n\n  // events\n  public chartClicked({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public chartHovered({\n    event,\n    active,\n  }: {\n    event?: ChartEvent;\n    active?: {}[];\n  }): void {}\n\n  public randomize(): void {\n    // Only Change 3 values\n    this.barChartData.datasets[0].data = [\n      Math.round(Math.random() * 100),\n      59,\n      80,\n      Math.round(Math.random() * 100),\n      56,\n      Math.round(Math.random() * 100),\n      40,\n    ];\n\n    this.chart?.update();\n  }\n  /** Based on the screen size, switch from standard to one column per row */\n  cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(\n    map(({ matches }) => {\n      if (matches) {\n        return [\n          { title: 'Card 1', cols: 1, rows: 1 },\n          { title: 'Card 2', cols: 1, rows: 1 },\n          { title: 'Card 3', cols: 1, rows: 1 },\n          { title: 'Card 4', cols: 1, rows: 1 },\n        ];\n      }\n\n      return [\n        { title: 'Card 1', cols: 2, rows: 1 },\n        { title: 'Card 2', cols: 1, rows: 1 },\n        { title: 'Card 3', cols: 1, rows: 2 },\n        { title: 'Card 4', cols: 1, rows: 1 },\n      ];\n    })\n  );\n\n  constructor(private breakpointObserver: BreakpointObserver) {}\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/dashboard/dashboard.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DashboardComponent } from './dashboard.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatGridListModule } from '@angular/material/grid-list';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatListModule } from '@angular/material/list';\nimport { MatMenuModule } from '@angular/material/menu';\n\nimport { NgChartsModule } from 'ng2-charts';\n\nimport { DashboardRoutingModule } from './dashboard-routing.module';\n\n@NgModule({\n  declarations: [DashboardComponent],\n  imports: [\n    CommonModule,\n    DashboardRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatGridListModule,\n    MatIconModule,\n    MatListModule,\n    MatMenuModule,\n    NgChartsModule,\n  ],\n})\nexport class DashboardModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/404.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">404</h1>\n          <h4 class=\"pt-3\">Oops! You're lost.</h4>\n          <p class=\"text-muted\">The page you are looking for was not found.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/404.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '404.component.html',\n})\nexport class P404Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/500.component.html",
    "content": "<div class=\"app flex-row align-items-center\">\n  <div class=\"container\">\n    <div class=\"row justify-content-center\">\n      <div class=\"col-md-6\">\n        <div class=\"clearfix\">\n          <h1 class=\"float-left display-3 mr-4\">500</h1>\n          <h4 class=\"pt-3\">Houston, we have a problem!</h4>\n          <p class=\"text-muted\">The page you are looking for is temporarily unavailable.</p>\n        </div>\n        <div class=\"input-prepend input-group\">\n          <div class=\"input-group-prepend\">\n            <span class=\"input-group-text\"><i class=\"fa fa-search\"></i></span>\n          </div>\n          <input id=\"prependedInput\" class=\"form-control\" size=\"16\" type=\"text\" placeholder=\"What are you looking for?\">\n          <span class=\"input-group-append\">\n            <button class=\"btn btn-info\" type=\"button\">Search</button>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/500.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component } from '@angular/core';\n\n@Component({\n  templateUrl: '500.component.html',\n})\nexport class P500Component {\n  constructor() {}\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/unauthorized.component.html",
    "content": "<!-- <alert type=\"danger\" *ngIf=\"error\" [dismissible]=\"true\">\n  <p class=\"text-center\"><strong>Oh snap!</strong> {{this.errorMessage}}</p>\n</alert> -->\n<div class=\"jumbotron jumbotron-fluid center-screen\">\n  <div>\n    <form [formGroup]=\"tenantForm\" (submit)=\"login()\">\n      <mat-card class=\"card\">\n        <mat-card-title>Unauthorized</mat-card-title>\n        <mat-card-subtitle>Enter your tenant name and click submit below</mat-card-subtitle>\n        <mat-card-content>\n          <mat-form-field appearance=\"outline\">\n            <mat-label>Tenant Name</mat-label>\n            <input\n            matInput\n            type=\"tenantname\"\n            id=\"tenantname\"\n            name=\"tenantname\"\n            class=\"form-control\"\n            formControlName=\"tenantName\"\n            placeholder=\"Enter Tenant Name\"\n            [ngClass]=\"displayFieldCss('tenantName')\"\n            required\n          />\n            <mat-icon matSuffix>home</mat-icon>\n          </mat-form-field>\n          <mat-card-actions>\n            <div class=\"button-panel\">\n              <button\n                mat-raised-button\n                color=\"primary\"\n                type=\"submit\"\n                [disabled]=\"!tenantForm.valid\"\n              >\n                Submit\n              </button>\n            </div>\n          </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n    </form>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/unauthorized.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n .center-screen {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n  min-height: 100vh;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/error/unauthorized.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { AuthConfigurationService } from './../auth/auth-configuration.service';\nimport { Observable } from 'rxjs';\nimport { Router } from '@angular/router';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-unauthorized',\n  templateUrl: './unauthorized.component.html',\n  styleUrls: ['./unauthorized.component.scss'],\n})\nexport class UnauthorizedComponent implements OnInit {\n  tenantForm: FormGroup;\n  params$: Observable<void>;\n  error = false;\n  errorMessage: string;\n\n  constructor(\n    private fb: FormBuilder,\n    private authConfigService: AuthConfigurationService,\n    private _snackBar: MatSnackBar,\n    private router: Router\n  ) {}\n\n  ngOnInit(): void {\n    this.tenantForm = this.fb.group({\n      tenantName: [null, [Validators.required]],\n    });\n\n    if (localStorage.getItem('tenantName')) {\n      this.router.navigate(['/dashboard']);\n    }\n  }\n\n  isFieldInvalid(field: string) {\n    const formField = this.tenantForm.get(field);\n    return (\n      formField && formField.invalid && (formField.dirty || formField.touched)\n    );\n  }\n\n  displayFieldCss(field: string) {\n    return {\n      'is-invalid': this.isFieldInvalid(field),\n    };\n  }\n\n  hasRequiredError(field: string) {\n    return !!this.tenantForm.get(field)?.hasError('required');\n  }\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  login() {\n    let tenantName = this.tenantForm.value.tenantName;\n    if (!tenantName) {\n      this.errorMessage = 'No tenant name provided.';\n      this.error = true;\n      this.openErrorMessageSnackBar(this.errorMessage);\n      return false;\n    }\n\n    this.authConfigService\n      .setTenantConfig(tenantName)\n      .then((val) => {\n        this.router.navigate(['/dashboard']);\n      })\n      .catch((errorResponse) => {\n        this.error = true;\n        this.errorMessage =\n          errorResponse.error.message || 'An unexpected error occurred!';\n        this.openErrorMessageSnackBar(this.errorMessage);\n      });\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/create/create.component.html",
    "content": "<div class=\"order-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <form [formGroup]=\"orderForm\" (submit)=\"submit()\">\n        <mat-card class=\"card\">\n          <mat-card-title>Create new order</mat-card-title>\n          <mat-card-content>\n            <mat-form-field>\n              <mat-label>Enter order name</mat-label>\n              <input\n                matInput\n                placeholder=\"order name\"\n                formControlName=\"orderName\"\n                required\n              />\n              <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n            </mat-form-field>\n            <div class=\"order-products\">\n              <div *ngFor=\"let op of orderProducts\" class=\"row\">\n                <div class=\"col-6\">{{ op.product.name }}</div>\n                <div class=\"col-3\">{{ op.product.price | currency }}</div>\n                <div class=\"col-1\">{{ op.quantity || 0 }}</div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"primary\"\n                    aria-label=\"Icon button with add icon\"\n                    (click)=\"add(op)\"\n                  >\n                    <mat-icon>add</mat-icon>\n                  </button>\n                </div>\n                <div class=\"col-1\">\n                  <button\n                    mat-mini-fab\n                    color=\"warn\"\n                    aria-label=\"Icon button with remove icon\"\n                    (click)=\"remove(op)\"\n                  >\n                    <mat-icon>remove</mat-icon>\n                  </button>\n                </div>\n              </div>\n              <mat-card *ngIf=\"isLoadingProducts\" style=\"display: flex; justify-content: center; align-items: center\">\n                <mat-progress-spinner\n                  color=\"primary\"\n                  mode=\"indeterminate\"\n                  diameter=\"15\"\n                >\n                </mat-progress-spinner>\n              </mat-card>\n        \n            </div>\n            <mat-card-footer>\n              <div class=\"button-panel\">\n                <button\n                  mat-raised-button\n                  color=\"primary\"\n                  type=\"submit\"\n                  [disabled]=\"!orderForm.valid || !productQuantity\"\n                >\n                  Submit\n                </button>\n                <button\n                  mat-raised-button\n                  color=\"warn\"\n                  [routerLink]=\"['/orders']\"\n                  type=\"button\"\n                >\n                  Cancel\n                </button>\n              </div>\n            </mat-card-footer>\n          </mat-card-content>\n        </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/create/create.component.scss",
    "content": "// .card {\n//   margin: 20px;\n//   display: flex;\n//   flex-direction: column;\n//   align-items: flex-start;\n// }\n\n.order-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { Product } from '../../products/models/product.interface';\nimport { ProductService } from '../../products/product.service';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\ninterface LineItem {\n  product: Product;\n  quantity?: number;\n}\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  displayedColumns = [];\n  orderForm: FormGroup;\n  orderProducts: LineItem[] = [];\n  isLoadingProducts: boolean = true;\n  error = '';\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private orderSvc: OrdersService,\n    private router: Router\n  ) {\n    this.orderForm = this.fb.group({\n      name: ['', Validators.required],\n    });\n  }\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((products) => {\n      this.orderProducts = products.map((p) => ({ product: p }));\n      this.isLoadingProducts = false;\n    });\n    this.orderForm = this.fb.group({\n      orderName: ['', Validators.required],\n    });\n  }\n\n  get name() {\n    return this.orderForm.get('name');\n  }\n\n  get productQuantity() {\n    return this.orderProducts\n        .filter((p) => !!p.quantity).length > 0;\n  }\n\n\n  add(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity: orderProduct.quantity ? orderProduct.quantity + 1 : 1,\n        };\n      }\n      return p;\n    });\n  }\n\n  remove(op: LineItem) {\n    const orderProduct = this.orderProducts.find(\n      (p) => p?.product.productId === op.product.productId\n    );\n    this.orderProducts = this.orderProducts.map((p) => {\n      if (p.product?.productId === orderProduct?.product?.productId) {\n        p = {\n          ...orderProduct,\n          quantity:\n            orderProduct.quantity && orderProduct.quantity > 1\n              ? orderProduct.quantity - 1\n              : undefined,\n        };\n      }\n      return p;\n    });\n  }\n\n  submit() {\n    const val: Order = {\n      ...this.orderForm?.value,\n      orderProducts: this.orderProducts\n        .filter((p) => !!p.quantity)\n        .map((p) => ({\n          productId: p.product.productId,\n          price: p.product.price,\n          quantity: p.quantity,\n        })),\n    };\n    this.orderSvc.create(val).subscribe(() => this.router.navigate(['orders']));\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/detail/detail.component.html",
    "content": "<div class=\"container invoice\">\n  <div class=\"invoice-header\">\n    <div class=\"row\">\n      <div class=\"col-xs-8\">\n        <h1>Order Details <small>mostly made up</small></h1>\n        <h5 class=\"text-muted\">\n          NO: {{ orderId$ | async }} | Date: {{ today() | date }}\n        </h5>\n      </div>\n      <div class=\"col-xs-4\">\n        <div class=\"media\">\n          <div class=\"media-left\">\n            <img class=\"media-object logo\" src=\"assets/logo.svg\" />\n          </div>\n          <ul class=\"media-body list-unstyled\">\n            <li>\n              <strong>{{ tenantName() }}</strong>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </div>\n  </div>\n  <div class=\"invoice-body\">\n    <div class=\"row\">\n      <div class=\"card\">\n        <div class=\"card-header\">\n          <h3>Services / Products</h3>\n        </div>\n        <table class=\"table table-bordered table-responsive no-bottom-margin\">\n          <thead>\n            <tr>\n              <th>Item / Details</th>\n              <th class=\"text-center colfix\">Unit Cost</th>\n              <th class=\"text-center colfix\">Sum Cost</th>\n              <th class=\"text-center colfix\">Discount</th>\n              <th class=\"text-center colfix\">Tax</th>\n              <th class=\"text-center colfix\">Total</th>\n            </tr>\n          </thead>\n          <tbody>\n            <tr *ngFor=\"let op of orderProducts$ | async\">\n              <td class=\"nowrap\">\n                {{ op.productId }}\n                <br />\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ op.price | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Before Tax</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">{{ sum(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">{{ op.quantity }} Units</small>\n              </td>\n              <td class=\"text-right\">\n                <span class=\"mono\">$0.00</span>\n                <br />\n                <small class=\"text-muted\">None</small>\n              </td>\n              <td class=\"text-right nowrap\">\n                <span class=\"mono\">{{ tax(op) | currency }}</span>\n                <br />\n                <small class=\"text-muted\">Sales Tax 8.9%</small>\n              </td>\n              <td class=\"text-right\">\n                <strong class=\"mono\">{{ total(op) | currency }}</strong>\n                <br />\n              </td>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n      <div>\n        <table\n          *ngIf=\"order$ | async as order\"\n          class=\"table table-bordered table-condensed\"\n        >\n          <thead>\n            <tr>\n              <td class=\"text-center col-xs-1\">Sub Total</td>\n              <td class=\"text-center col-xs-1\">Discount</td>\n              <td class=\"text-center col-xs-1\">Total</td>\n              <td class=\"text-center col-xs-1\">Tax</td>\n              <td class=\"text-center col-xs-1\">Final</td>\n            </tr>\n          </thead>\n          <tbody>\n            <tr>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">-$0.00</th>\n              <th class=\"text-center rowtotal mono\">\n                {{ subTotal(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ calcTax(order) | currency }}\n              </th>\n              <th class=\"text-center rowtotal mono\">\n                {{ final(order) | currency }}\n              </th>\n            </tr>\n          </tbody>\n        </table>\n      </div>\n    </div>\n    <div class=\"row\">\n      <div class=\"col-xs-7\">\n        <div class=\"card\">\n          <div class=\"card-body\">\n            <i>Comments / Notes</i>\n            <hr style=\"margin: 3px 0 5px\" />\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit\n            repudiandae numquam sit facere blanditiis, quasi distinctio ipsam?\n            Libero odit ex expedita, facere sunt, possimus consectetur dolore,\n            nobis iure amet vero.\n          </div>\n        </div>\n      </div>\n    </div>\n    <div class=\"invoice-footer\">\n      Thank you for choosing the Serverless SaaS Reference Architecture.\n      <br />\n      We hope to see you again soon\n      <br />\n      <strong>AWS</strong>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/detail/detail.component.scss",
    "content": "/*\n * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n.no-bottom-margin {\n  margin-bottom: 0;\n}\n\n.nowrap {\n  white-space: nowrap;\n}\n\n.invoice {\n  font-family: Arial, Helvetica, sans-serif;\n  width: 970px !important;\n  margin: 50px auto;\n  .invoice-header {\n    padding: 25px 25px 15px;\n    h1 {\n      margin: 0;\n    }\n    .media {\n      .media-body {\n        font-size: 0.9em;\n        margin: 0;\n      }\n    }\n  }\n  .invoice-body {\n    border-radius: 10px;\n    padding: 25px;\n    background: #fff;\n  }\n  .invoice-footer {\n    padding: 15px;\n    font-size: 0.9em;\n    text-align: center;\n    color: #999;\n  }\n}\n.logo {\n  max-height: 70px;\n  border-radius: 10px;\n}\n.dl-horizontal {\n  margin: 0;\n  dt {\n    float: left;\n    width: 80px;\n    overflow: hidden;\n    clear: left;\n    text-align: right;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  dd {\n    margin-left: 90px;\n  }\n}\n.rowamount {\n  padding-top: 15px !important;\n}\n.rowtotal {\n  font-size: 1.3em;\n}\n.colfix {\n  width: 12%;\n}\n.mono {\n  font-family: monospace;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/detail/detail.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Order } from '../models/order.interface';\nimport { OrderProduct } from '../models/orderproduct.interface';\nimport { OrdersService } from '../orders.service';\n@Component({\n  selector: 'app-detail',\n  templateUrl: './detail.component.html',\n  styleUrls: ['./detail.component.scss'],\n})\nexport class DetailComponent implements OnInit {\n  orderId$: Observable<string> | undefined;\n  order$: Observable<Order> | undefined;\n  orderProducts$: Observable<OrderProduct[]> | undefined;\n  taxRate = 0.0899;\n  constructor(private route: ActivatedRoute, private orderSvc: OrdersService) {}\n\n  ngOnInit(): void {\n    this.orderId$ = this.route.params.pipe(map((o) => o['orderId']));\n    this.order$ = this.orderId$.pipe(switchMap((o) => this.orderSvc.get(o)));\n    this.orderProducts$ = this.order$.pipe(map((o) => o.orderProducts));\n  }\n\n  today() {\n    return new Date();\n  }\n\n  tenantName() {\n    return '';\n  }\n\n  sum(op: OrderProduct) {\n    return op.price * op.quantity;\n  }\n\n  tax(op: OrderProduct) {\n    return this.sum(op) * this.taxRate;\n  }\n\n  total(op: OrderProduct) {\n    return this.sum(op) + this.tax(op);\n  }\n\n  subTotal(order: Order) {\n    return order.orderProducts\n      .map((op) => op.price * op.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n\n  calcTax(order: Order) {\n    return this.subTotal(order) * this.taxRate;\n  }\n\n  final(order: Order) {\n    return this.subTotal(order) + this.calcTax(order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/list/list.component.html",
    "content": "<div class=\"order-list\">\n  <h2>Order List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"orderData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">\n            <a\n              class=\"link\"\n              routerLink=\"/orders/detail/{{ element.shardId }}:{{\n                element.orderId\n              }}\"\n            >\n              {{ element.orderName }}\n            </a>\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"lineItems\">\n          <th mat-header-cell *matHeaderCellDef>Line Items</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.orderProducts?.length }}\n          </td>\n        </ng-container>\n\n        <ng-container matColumnDef=\"total\">\n          <th mat-header-cell *matHeaderCellDef>Total</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ sum(element) | currency }}\n          </td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card\n        *ngIf=\"isLoading\"\n        style=\"display: flex; justify-content: center; align-items: center\"\n      >\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button\n          routerLink=\"/orders/create\"\n          routerLinkActive=\"router-link-active\"\n          mat-raised-button\n          color=\"primary\"\n        >\n          Create Order\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/list/list.component.scss",
    "content": ".order-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Order } from '../models/order.interface';\nimport { OrdersService } from '../orders.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  displayedColumns: string[] = ['name', 'lineItems', 'total'];\n  orderData: Order[] = [];\n  isLoading: boolean = true;\n  constructor(private orderSvc: OrdersService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.orderSvc.fetch().subscribe((data) => {\n      this.isLoading = false;\n      this.orderData = data;\n    });\n  }\n\n  sum(order: Order): number {\n    return order.orderProducts\n      .map((p) => p.price * p.quantity)\n      .reduce((acc, curr) => acc + curr);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/models/order.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { OrderProduct } from './orderproduct.interface';\n\nexport interface Order {\n  key: string;\n  shardId: string;\n  orderId: string;\n  orderName: string;\n  orderProducts: OrderProduct[];\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/models/orderproduct.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface OrderProduct {\n  productId: string;\n  price: number;\n  quantity: number;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/orders-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'full',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Orders',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create Order',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'detail/:orderId',\n    data: {\n      title: 'View Order Detail',\n    },\n    component: DetailComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class OrdersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/orders.module.ts",
    "content": "import { CommonModule } from '@angular/common';\nimport { NgModule } from '@angular/core';\nimport { ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTableModule } from '@angular/material/table';\n\nimport { CreateComponent } from './create/create.component';\nimport { DetailComponent } from './detail/detail.component';\nimport { ListComponent } from './list/list.component';\nimport { OrdersRoutingModule } from './orders-routing.module';\n\n@NgModule({\n  declarations: [CreateComponent, ListComponent, DetailComponent],\n  imports: [\n    CommonModule,\n    OrdersRoutingModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatIconModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class OrdersModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/orders/orders.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Order } from './models/order.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class OrdersService {\n  orders: Order[] = [];\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n  constructor(private http: HttpClient) {}\n\n  fetch(): Observable<Order[]> {\n    return this.http.get<Order[]>(`${this.baseUrl}/orders`);\n  }\n\n  get(orderId: string): Observable<Order> {\n    const url = `${this.baseUrl}/order/${orderId}`;\n    return this.http.get<Order>(url);\n  }\n\n  create(order: Order): Observable<Order> {\n    return this.http.post<Order>(`${this.baseUrl}/order`, order);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/create/create.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm\" (submit)=\"submit()\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>SKU</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"sku\"\n            placeholder=\"Enter product sku\"\n          />\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Category</mat-label>\n          <mat-select\n            formControlName=\"category\"\n            required\n          >\n            <mat-option *ngFor=\"let category of categories\" [value]=\"category\">\n              {{category}}\n            </mat-option>\n          </mat-select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button\n              mat-raised-button\n              color=\"primary\"\n              (click)=\"(submit)\"\n              [disabled]=\"!productForm.valid\"\n            >\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/create/create.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.product-form {\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n}\n\n.mat-form-field {\n  display: flex;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/create/create.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { Router } from '@angular/router';\n\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  productForm: FormGroup;\n  categories: string[] = ['category1', 'category2', 'category3', 'category4'];\n  constructor(\n    private fb: FormBuilder,\n    private productSvc: ProductService,\n    private router: Router\n  ) {\n    this.productForm = this.fb.group({});\n  }\n\n  ngOnInit(): void {\n    this.productForm = this.fb.group({\n      name: ['', Validators.required],\n      price: ['', Validators.required],\n      sku: '',\n      category: '',\n    });\n  }\n\n  get name() {\n    return this.productForm.get('name');\n  }\n\n  get price() {\n    return this.productForm.get('price');\n  }\n\n  submit() {\n    this.productSvc.post(this.productForm.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err.message);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/edit/edit.component.html",
    "content": "<div class=\"product-form\">\n  <form [formGroup]=\"productForm!\">\n    <mat-card class=\"card\">\n      <mat-card-title>Create new Product</mat-card-title>\n      <mat-card-content>\n        <mat-form-field>\n          <mat-label>Enter product name</mat-label>\n          <input\n            matInput\n            placeholder=\"Product name\"\n            formControlName=\"name\"\n            required\n          />\n          <mat-error *ngIf=\"name?.invalid\">Name is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Enter product price</mat-label>\n          <input\n            type=\"number\"\n            matInput\n            placeholder=\"Product price\"\n            formControlName=\"price\"\n            required\n          />\n          <mat-error *ngIf=\"price?.invalid\">Price is required</mat-error>\n        </mat-form-field>\n        <mat-form-field>\n          <mat-label>Description</mat-label>\n          <input\n            matInput\n            type=\"text\"\n            formControlName=\"description\"\n            placeholder=\"Enter product description\"\n          />\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <button mat-raised-button color=\"primary\" (click)=\"(submit)\">\n              Submit\n            </button>\n            <button mat-raised-button color=\"warn\" (click)=\"cancel()\">\n              Cancel\n            </button>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/edit/edit.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/edit/edit.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { Observable, of } from 'rxjs';\nimport { map, switchMap } from 'rxjs/operators';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n@Component({\n  selector: 'app-edit',\n  templateUrl: './edit.component.html',\n  styleUrls: ['./edit.component.scss'],\n})\nexport class EditComponent implements OnInit {\n  productForm: FormGroup | undefined;\n  product$: Observable<Product | undefined> | undefined;\n  productId$: Observable<string> | undefined;\n  productName$: Observable<string | undefined> | undefined;\n\n  constructor(\n    private route: ActivatedRoute,\n    private router: Router,\n    private productSvc: ProductService,\n    private fb: FormBuilder\n  ) {}\n\n  ngOnInit(): void {\n    this.productId$ = this.route.params.pipe(map((p) => p['productId']));\n    this.product$ = this.productId$.pipe(\n      switchMap((p) => this.productSvc.get(p))\n    );\n    this.productName$ = this.product$.pipe(map((p) => p?.name));\n\n    this.productForm = this.fb.group({\n      productId: [''],\n      name: [''],\n      price: [''],\n      description: [''],\n    });\n\n    this.product$.subscribe((val) => {\n      this.productForm?.patchValue({\n        ...val,\n      });\n    });\n  }\n\n  get name() {\n    return this.productForm?.get('name');\n  }\n\n  get price() {\n    return this.productForm?.get('price');\n  }\n\n  submit() {\n    this.productSvc.put(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => console.error(err),\n    });\n  }\n\n  delete() {\n    this.productSvc.delete(this.productForm?.value).subscribe({\n      next: () => this.router.navigate(['products']),\n      error: (err) => {\n        alert(err);\n        console.error(err);\n      },\n    });\n  }\n\n  cancel() {\n    this.router.navigate(['products']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/list/list.component.html",
    "content": "<div class=\"product-list\">\n  <h2>Product List</h2>\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n      <table mat-table [dataSource]=\"productData\" class=\"mat-elevation-z1\">\n        <!-- Name Column -->\n        <ng-container matColumnDef=\"name\">\n          <th mat-header-cell *matHeaderCellDef>Name</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.name }}</td>\n        </ng-container>\n\n        <!-- Price Column -->\n        <ng-container matColumnDef=\"price\">\n          <th mat-header-cell *matHeaderCellDef>Price</th>\n          <td mat-cell *matCellDef=\"let element\">\n            {{ element.price | currency }}\n          </td>\n        </ng-container>\n\n        <!-- Description Column -->\n        <ng-container matColumnDef=\"sku\">\n          <th mat-header-cell *matHeaderCellDef>SKU</th>\n          <td mat-cell *matCellDef=\"let element\">{{ element.sku }}</td>\n        </ng-container>\n\n        <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n        <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n      </table>\n      <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n        <mat-progress-spinner\n          color=\"primary\"\n          mode=\"indeterminate\"\n          diameter=\"15\"\n        >\n        </mat-progress-spinner>\n      </mat-card>\n      <div class=\"button-panel\">\n        <button (click)=\"onCreate()\" mat-raised-button color=\"primary\">\n          Create Product\n        </button>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/list/list.component.scss",
    "content": ".product-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/list/list.component.ts",
    "content": "import { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { Product } from '../models/product.interface';\nimport { ProductService } from '../product.service';\n\n@Component({\n  selector: 'app-list',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  productData: Product[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = ['name', 'price', 'sku'];\n\n  constructor(private productSvc: ProductService, private router: Router) {}\n\n  ngOnInit(): void {\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onEdit(product: Product) {\n    this.router.navigate(['products', 'edit', product.productId]);\n    return false;\n  }\n\n  onRemove(product: Product) {\n    this.productSvc.delete(product);\n    this.isLoading = true;\n    this.productSvc.fetch().subscribe((data) => {\n      this.productData = data;\n      this.isLoading = false;\n    });\n  }\n\n  onCreate() {\n    this.router.navigate(['products', 'create']);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/models/product.interface.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface Product {\n  key: string;\n  shardId: string;\n  productId: string;\n  name: string;\n  price: number;\n  sku: string;\n  category: string;\n  pictureUrl?: string;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/product.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { Product } from './models/product.interface';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class ProductService {\n  constructor(private http: HttpClient) {}\n  baseUrl = `${localStorage.getItem('apiGatewayUrl')}`;\n\n  fetch(): Observable<Product[]> {\n    return this.http.get<Product[]>(`${this.baseUrl}/products`);\n  }\n\n  get(productId: string): Observable<Product> {\n    const url = `${this.baseUrl}/product/${productId}`;\n    return this.http.get<Product>(url);\n  }\n\n  delete(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.delete<Product>(url);\n  }\n\n  put(product: Product) {\n    const url = `${this.baseUrl}/product/${product.shardId}:${product.productId}`;\n    return this.http.put<Product>(url, product);\n  }\n  post(product: Product) {\n    return this.http.post<Product>(`${this.baseUrl}/product`, product);\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/products-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'Product List',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create new Product',\n    },\n    component: CreateComponent,\n  },\n  {\n    path: 'edit/:productId',\n    data: {\n      title: 'Edit Product',\n    },\n    component: EditComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class ProductsRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/products/products.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\nimport { ProductsRoutingModule } from './products-routing.module';\nimport { CreateComponent } from './create/create.component';\nimport { EditComponent } from './edit/edit.component';\nimport { ListComponent } from './list/list.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  declarations: [CreateComponent, EditComponent, ListComponent],\n  imports: [\n    CommonModule,\n    ReactiveFormsModule,\n    ProductsRoutingModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatProgressSpinnerModule,\n  ],\n  providers: [\n    {\n      provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,\n      useValue: { appearance: 'outline' },\n    },\n  ],\n})\nexport class ProductsModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/create/create.component.html",
    "content": "<div class=\"user-form container\">\n  <div class=\"row\">\n    <div class=\"col-md-6\">\n\n      <form [formGroup]=\"userForm\">\n      <mat-card class=\"card\">\n        <mat-card-title>Create User</mat-card-title>\n        <mat-card-subtitle>Upon submission, a new user account will be created and we will send an email to the provided address with login instructions.</mat-card-subtitle>\n        <mat-card-content>\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Username</mat-label>\n              <input\n                matInput\n                id=\"username\"\n                name=\"username\"\n                formControlName=\"userName\"\n                required\n              />\n              <mat-icon matSuffix>person</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>Email</mat-label>\n              <input\n                matInput\n                id=\"useremail\"\n                name=\"useremail\"\n                formControlName=\"userEmail\"\n                required\n              />\n              <mat-icon matSuffix>email</mat-icon>\n              <mat-error *ngIf=\"userForm.get('userEmail')?.errors\">A valid email must be provided.</mat-error>\n            </mat-form-field>\n            <br />\n\n            <mat-form-field appearance=\"outline\">\n              <mat-label>User Role</mat-label>\n              <input\n                matInput\n                id=\"userrole\"\n                name=\"userrole\"\n                formControlName=\"userRole\"\n                placeholder=\"User Role\"\n                required\n              />\n              <mat-icon matSuffix>vpn_key</mat-icon>\n            </mat-form-field>\n            <br />\n\n            <mat-card-actions>\n              <div class=\"button-panel\">\n                <button\n                mat-raised-button\n                class=\"btn btn-block btn-primary\"\n                [disabled]=\"!userForm.valid\"\n                (click)=\"onSubmit()\"\n                type=\"button\"\n                >\n                  <span>Create</span>\n                </button>\n                <button\n                  mat-raised-button\n                  class=\"btn btn-block btn-danger\"\n                  routerLink=\"/users\"\n                  type=\"button\"\n                >\n                  <span>Cancel</span>\n                </button>\n              </div>\n            </mat-card-actions>\n        </mat-card-content>\n      </mat-card>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/create/create.component.scss",
    "content": ".user-form {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n}\n\n.mat-card-title {\n  display: flex;\n  justify-content: center;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\n.row {\n  width: 100%;\n  align-items: center;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/create/create.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { UsersService } from '../users.service';\nimport { MatSnackBar } from '@angular/material/snack-bar';\n\n@Component({\n  selector: 'app-create',\n  templateUrl: './create.component.html',\n  styleUrls: ['./create.component.scss'],\n})\nexport class CreateComponent implements OnInit {\n  userForm: FormGroup;\n  error: boolean = false;\n  success: boolean = false;\n\n  constructor(\n    private fb: FormBuilder,\n    private userSvc: UsersService,\n    private _snackBar: MatSnackBar\n  ) {\n    this.userForm = this.fb.group({\n      userName: [null, [Validators.required]],\n      userEmail: [null, [Validators.email, Validators.required]],\n      userRole: [null, [Validators.required]],\n    });\n  }\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  onSubmit() {\n    const user = this.userForm.value;\n    this.userSvc.create(user).subscribe(\n      () => {\n        this.success = true;\n        this.openErrorMessageSnackBar('Successfully created new user!');\n      },\n      (err) => {\n        this.error = true;\n        this.openErrorMessageSnackBar('An unexpected error occurred!');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/list/list.component.html",
    "content": "<div class=\"user-list\">\n  <mat-card class=\"card\">\n    <mat-card-content>\n      <mat-card-title>\n        User List\n      </mat-card-title>\n      <div class=\"row\">\n        <div class=\"col-md-12\">\n          <table mat-table [dataSource]=\"userData\" class=\"mat-elevation-z8\" style=\"width: 100%;\">\n            <!-- Email Column -->\n            <ng-container matColumnDef=\"email\">\n              <th mat-header-cell *matHeaderCellDef>Email</th>\n              <td mat-cell *matCellDef=\"let element\">{{ element.email }}</td>\n            </ng-container>\n\n            <!-- Created Date Column -->\n            <ng-container matColumnDef=\"created\">\n              <th mat-header-cell *matHeaderCellDef>Created Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.created | date }}\n              </td>\n            </ng-container>\n\n            <!-- Modified Date Column -->\n            <ng-container matColumnDef=\"modified\">\n              <th mat-header-cell *matHeaderCellDef>Modified Date</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.modified | date }}\n              </td>\n            </ng-container>\n\n            <!-- Status Column -->\n            <ng-container matColumnDef=\"status\">\n              <th mat-header-cell *matHeaderCellDef>Status</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.status }}\n              </td>\n            </ng-container>\n\n            <!-- Enabled Column -->\n            <ng-container matColumnDef=\"enabled\">\n              <th mat-header-cell *matHeaderCellDef>Enabled</th>\n              <td mat-cell *matCellDef=\"let element\">\n                {{ element.enabled }}\n              </td>\n            </ng-container>\n            <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n            <tr mat-row *matRowDef=\"let row; columns: displayedColumns\"></tr>\n          </table>\n          <mat-card *ngIf=\"isLoading\" style=\"display: flex; justify-content: center; align-items: center\">\n            <mat-progress-spinner\n              color=\"primary\"\n              mode=\"indeterminate\"\n              diameter=\"15\"\n            >\n            </mat-progress-spinner>\n          </mat-card>\n        </div>\n      </div>\n      <mat-card-actions>\n        <div class=\"button-panel\">\n          <button routerLink=\"/users/create\" mat-raised-button color=\"primary\">Add User</button>\n        </div>\n      </mat-card-actions>\n    </mat-card-content>\n  </mat-card>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/list/list.component.scss",
    "content": ".user-list {\n  margin: 20px;\n}\ntable {\n  width: 100%;\n}\n\n.button-panel {\n  margin-top: 20px;\n}\n\n.mat-cell {\n  margin: 20px 5px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/list/list.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { Component, OnInit } from '@angular/core';\nimport { User } from '../models/user';\nimport { UsersService } from '../users.service';\n\n@Component({\n  selector: 'app-user',\n  templateUrl: './list.component.html',\n  styleUrls: ['./list.component.scss'],\n})\nexport class ListComponent implements OnInit {\n  userData: User[] = [];\n  isLoading: boolean = true;\n  displayedColumns: string[] = [\n    'email',\n    'created',\n    'modified',\n    'status',\n    'enabled',\n  ];\n\n  constructor(private userSvc: UsersService) {}\n\n  ngOnInit(): void {\n    this.userSvc.fetch().subscribe((data) => {\n      this.userData = data;\n      this.isLoading = false;\n    });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/models/user.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nexport interface User {\n  email: string;\n  created?: string;\n  modified?: string;\n  enabled?: boolean;\n  status?: string;\n  verified?: boolean;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/users-routing.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { Routes, RouterModule } from '@angular/router';\nimport { CreateComponent } from './create/create.component';\nimport { ListComponent } from './list/list.component';\n\nconst routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'list',\n    pathMatch: 'prefix',\n  },\n  {\n    path: 'list',\n    data: {\n      title: 'All Users',\n    },\n    component: ListComponent,\n  },\n  {\n    path: 'create',\n    data: {\n      title: 'Create User',\n    },\n    component: CreateComponent,\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forChild(routes)],\n  exports: [RouterModule],\n})\nexport class UsersRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/users.module.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n * SPDX-License-Identifier: MIT-0\n */\nimport { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\nimport { UsersRoutingModule } from './users-routing.module';\nimport { ListComponent } from './list/list.component';\nimport { CreateComponent } from './create/create.component';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\n\nimport {\n  MatFormFieldModule,\n  MAT_FORM_FIELD_DEFAULT_OPTIONS,\n} from '@angular/material/form-field';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { MatDialogModule } from '@angular/material/dialog';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@NgModule({\n  declarations: [ListComponent, CreateComponent],\n  imports: [\n    CommonModule,\n    UsersRoutingModule,\n    FormsModule,\n    ReactiveFormsModule,\n    MatButtonModule,\n    MatCardModule,\n    MatDatepickerModule,\n    MatDialogModule,\n    MatFormFieldModule,\n    MatInputModule,\n    MatRadioModule,\n    MatSelectModule,\n    MatTableModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSnackBarModule,\n  ],\n})\nexport class UsersModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/app/views/users/users.service.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { HttpClient, HttpHeaders } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { Observable, of } from 'rxjs';\nimport { find, mergeMap, defaultIfEmpty } from 'rxjs/operators';\nimport { User } from './models/user';\nimport { environment } from '../../../environments/environment';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class UsersService {\n  apiUrl: string;\n\n  constructor(private http: HttpClient) {\n    this.apiUrl = environment.regApiGatewayUrl;\n  }\n\n  fetch(): Observable<User[]> {\n    return this.http.get<User[]>(this.apiUrl + '/users');\n  }\n\n  create(user: User): Observable<User> {\n    return this.http.post<User>(this.apiUrl + '/user', user);\n  }\n\n  update(email: string, user: User) {}\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Application/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  regApiGatewayUrl:\n    'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  regApiGatewayUrl:\n    'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Application</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@import url(\"https://fonts.googleapis.com/icon?family=Material+Symbols|Material+Symbols+Outlined\");\n@import \"~@aws-amplify/ui-angular/theme.css\";\n\n@import \"~bootstrap/scss/functions\";\n@import \"styles/variables\";\n\n// Import functions, variables, and mixins needed by other Bootstrap files\n@import \"~bootstrap/scss/variables\";\n@import \"~bootstrap/scss/maps\";\n@import \"~bootstrap/scss/mixins\";\n\n// Import Bootstrap Reboot\n@import \"~bootstrap/scss/root\"; // Contains :root CSS variables used by other Bootstrap files\n@import \"~bootstrap/scss/reboot\";\n\n@import \"~bootstrap/scss/containers\"; // Add .container and .container-fluid classes\n@import \"~bootstrap/scss/grid\"; // Add the grid system\n\n@import \"~bootstrap/scss/utilities\"; // Configures the utility classes that should be generated\n@import \"~bootstrap/scss/utilities/api\"; // Generates the actual utility classes\n\n@import \"styles/reset\";\n\n.chart-container canvas {\n    max-height: 250px;\n    width: auto;\n}\n\n.chart-container {\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n}"
  },
  {
    "path": "Solution/Lab6/client/Application/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab6/client/Application/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"strictPropertyInitialization\": false,\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Application/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/.browserslistrc",
    "content": "# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.\n# For additional information regarding the format and rule options, please see:\n# https://github.com/browserslist/browserslist#queries\n\n# For the full list of supported browsers by the Angular framework, please see:\n# https://angular.io/guide/browser-support\n\n# You can see what browsers were selected by your queries by running:\n#   npx browserslist\n\nlast 1 Chrome version\nlast 1 Firefox version\nlast 2 Edge major versions\nlast 2 Safari major versions\nlast 2 iOS major versions\nFirefox ESR\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/.editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/.gitignore",
    "content": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n\n#amplify-do-not-edit-begin\namplify/\\#current-cloud-backend\namplify/.config/local-*\namplify/logs\namplify/mock-data\namplify/backend/amplify-meta.json\namplify/backend/.temp\nbuild/\ndist/\nnode_modules/\naws-exports.js\nawsconfiguration.json\namplifyconfiguration.json\namplifyconfiguration.dart\namplify-build-config.json\namplify-gradle-config.json\namplifytools.xcconfig\n.secret-*\n**.sample\n#amplify-do-not-edit-end\n\n# ignore yarn.lock and package-lock.json files for now\nyarn.lock\npackage-lock.json\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/README.md",
    "content": "# Landing\n\nThis project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.5.\n\n## Development server\n\nRun `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.\n\n## Code scaffolding\n\nRun `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.\n\n## Build\n\nRun `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.\n\n## Running unit tests\n\nRun `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).\n\n## Running end-to-end tests\n\nRun `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.\n\n## Further help\n\nTo get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"cli\": {\n    \"packageManager\": \"yarn\",\n    \"analytics\": false\n  },\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"Landing\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"\",\n      \"sourceRoot\": \"src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:browser\",\n          \"options\": {\n            \"outputPath\": \"dist\",\n            \"index\": \"src/index.html\",\n            \"main\": \"src/main.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"node_modules/bootstrap/dist/css/bootstrap.min.css\",\n              \"src/custom-theme.scss\",\n              \"src/styles.scss\",\n              \"node_modules/font-awesome/css/font-awesome.min.css\"\n            ]\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"10mb\",\n                  \"maximumError\": \"10mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"6kb\",\n                  \"maximumError\": \"10kb\"\n                }\n              ],\n              \"fileReplacements\": [\n                {\n                  \"replace\": \"src/environments/environment.ts\",\n                  \"with\": \"src/environments/environment.prod.ts\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"buildOptimizer\": false,\n              \"optimization\": false,\n              \"vendorChunk\": true,\n              \"extractLicenses\": false,\n              \"sourceMap\": true,\n              \"namedChunks\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"browserTarget\": \"Landing:build:production\"\n            },\n            \"development\": {\n              \"browserTarget\": \"Landing:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"browserTarget\": \"Landing:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"main\": \"src/test.ts\",\n            \"polyfills\": \"src/polyfills.ts\",\n            \"tsConfig\": \"tsconfig.spec.json\",\n            \"karmaConfig\": \"karma.conf.js\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              \"src/favicon.ico\",\n              \"src/assets\"\n            ],\n            \"styles\": [\n              \"src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, './coverage/dashboard'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    autoWatch: true,\n    browsers: ['Chrome'],\n    singleRun: false,\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/package.json",
    "content": "{\n  \"name\": \"landing\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"ng\": \"ng\",\n    \"start\": \"ng serve\",\n    \"build\": \"ng build\",\n    \"watch\": \"ng build --watch --configuration development\",\n    \"test\": \"ng test\"\n  },\n  \"private\": true,\n  \"dependencies\": {\n    \"@angular/animations\": \"~14.0.0\",\n    \"@angular/cdk\": \"~14.0.4\",\n    \"@angular/common\": \"~14.0.0\",\n    \"@angular/compiler\": \"~14.0.0\",\n    \"@angular/core\": \"~14.0.0\",\n    \"@angular/forms\": \"~14.0.0\",\n    \"@angular/material\": \"~14.0.4\",\n    \"@angular/platform-browser\": \"~14.0.0\",\n    \"@angular/platform-browser-dynamic\": \"~14.0.0\",\n    \"@angular/router\": \"~14.0.0\",\n    \"bootstrap\": \"~5.1.3\",\n    \"font-awesome\": \"~4.7.0\",\n    \"rxjs\": \"~7.5.0\",\n    \"tslib\": \"~2.3.0\",\n    \"zone.js\": \"~0.11.4\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"~14.1.0\",\n    \"@angular/cli\": \"~14.0.5\",\n    \"@angular/compiler-cli\": \"~14.0.0\",\n    \"@types/jasmine\": \"~4.0.0\",\n    \"jasmine-core\": \"~4.1.0\",\n    \"karma\": \"~6.3.0\",\n    \"karma-chrome-launcher\": \"~3.1.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.0.0\",\n    \"karma-jasmine-html-reporter\": \"~1.7.0\",\n    \"typescript\": \"~4.7.2\"\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/app-routing.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n  {\n    path: 'landing',\n    component: LandingComponent,\n  },\n  {\n    path: 'register',\n    component: RegisterComponent,\n  },\n  {\n    path: '**',\n    redirectTo: 'landing',\n    pathMatch: 'full',\n  },\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule],\n})\nexport class AppRoutingModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/app.component.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/app.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconRegistry } from '@angular/material/icon';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Component({\n  selector: 'app-root',\n  template: ` <router-outlet></router-outlet> `,\n  styleUrls: ['./app.component.scss'],\n})\nexport class AppComponent {\n  constructor(\n    private matIconRegistry: MatIconRegistry,\n    private domSanitizer: DomSanitizer\n  ) {\n    this.matIconRegistry.addSvgIcon(\n      'saas-commerce',\n      this.domSanitizer.bypassSecurityTrustResourceUrl('./assets/logo.svg')\n    );\n  }\n  title = 'landing';\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/app.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { BrowserAnimationsModule } from '@angular/platform-browser/animations';\nimport { HttpClientModule } from '@angular/common/http';\nimport { HashLocationStrategy, LocationStrategy } from '@angular/common';\nimport { FormsModule, ReactiveFormsModule } from '@angular/forms';\n\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatSidenavModule } from '@angular/material/sidenav';\nimport { MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\n\nimport { AppComponent } from './app.component';\nimport { AppRoutingModule } from './app-routing.module';\nimport { RegisterComponent } from './views/register/register.component';\nimport { LandingComponent } from './views/landing/landing.component';\n\n@NgModule({\n  declarations: [AppComponent, LandingComponent, RegisterComponent],\n  imports: [\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    HttpClientModule,\n    MatButtonModule,\n    MatCardModule,\n    MatIconModule,\n    MatProgressSpinnerModule,\n    MatSidenavModule,\n    AppRoutingModule,\n    BrowserAnimationsModule,\n    BrowserModule,\n    FormsModule,\n    HttpClientModule,\n    ReactiveFormsModule,\n    MatSnackBarModule,\n    MatInputModule,\n    MatFormFieldModule,\n  ],\n  providers: [\n    HttpClientModule,\n    {\n      provide: LocationStrategy,\n      useClass: HashLocationStrategy,\n    },\n  ],\n  bootstrap: [AppComponent],\n})\nexport class AppModule {}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/views/landing/landing.component.html",
    "content": "<div class=\"animated fadeIn\">\n  <nav class=\"navbar navbar-expand-lg navbar-light bg-light\">\n    <a class=\"navbar-brand\" href=\"#\">AWS Serverless SaaS Reference Architecture</a>\n    <button\n      aria-controls=\"navbarSupportedContent\"\n      class=\"navbar-toggler\"\n      type=\"button\"\n      aria-label=\"Toggle navigation\"\n    >\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n\n    <div\n      class=\"collapse navbar-collapse\"\n      id=\"navbarSupportedContent\"\n    >\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"#\"\n            >Home <span class=\"sr-only\">(current)</span></a\n          >\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" routerLink=\"/register\">Sign Up!</a>\n        </li>\n      </ul>\n    </div>\n  </nav>\n\n  <header class=\"d-flex align-items-center\">\n    <div class=\"container\">\n      <h1>Serverless SaaS Reference Architecture</h1>\n      <h2>It's so nice it blows your mind.</h2>\n      <a href=\"\" (click)=\"register()\" class=\"btn btn-transparent\">Sign up now!</a>\n    </div>\n  </header>\n\n  <section>\n    <div class=\"container\">\n      <div class=\"row\">\n        <div class=\"text--center\">\n          <h3>Serverless SaaS Reference Architecture is so awesome.</h3>\n          <p>\n            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facere\n            temporibus omnis illum, officia. Architecto voluptatibus commodi\n            voluptatem perspiciatis eos possimus, eius at molestias quaerat\n            magnam? Odio qui quos ipsam natus.\n          </p>\n        </div>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary\">\n    <div class=\"container\">\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bolt\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so awesome. Makes you awesome - go sign up!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-bank\"></i>\n        <p>\n          Serverless SaaS Reference Architecture so great. Makes you even greater - go sign\n          up now. Super cheap deal!\n        </p>\n      </div>\n      <div class=\"col-3 features\">\n        <i class=\"fa fa-heart\"></i>\n        <p>Feel lonely? Go sign up and have a friend!</p>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt\">\n    <div class=\"container\">\n      <h3>Take Serverless SaaS Reference Architecture with you everywhere you go.</h3>\n      <p>\n        Serverless SaaS Reference Architecture is all you need. Anywhere - ever. Lorem ipsum\n        dolor sit amet, consectetur adipisicing elit. Expedita sapiente hic\n        voluptatum quo sunt totam accusamus distinctio minus aliquid quis!\n      </p>\n    </div>\n  </section>\n\n  <section class=\"section--primary--light\">\n    <div class=\"container\">\n      <blockquote class=\"testimonial\">\n        <p>\n          Love Serverless SaaS Reference Architecture. So nice! So good! Could not live\n          without!\n        </p>\n        <cite> Satisfied Customer </cite>\n      </blockquote>\n    </div>\n  </section>\n\n  <section class=\"section--primary--alt bg-image-2\">\n    <div class=\"container text--center\">\n      <h3>Reasons to sign up this product:</h3>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n      <div class=\"col-5 text--left\">\n        <ul>\n          <li>Its the best</li>\n          <li>Its awesome</li>\n          <li>It makes you happy</li>\n          <li>It brings world peace</li>\n          <li>Its free!</li>\n        </ul>\n      </div>\n    </div>\n  </section>\n\n  <section class=\"text--center\">\n    <div class=\"container\">\n      <h3>Why you still reading?</h3>\n      <a href=\"\" (click)=\"register()\" class=\"btn\">Sign up now!</a>\n    </div>\n  </section>\n\n  <footer>\n    <div class=\"container\">\n      <ul>\n        <li><a href=\"#\">SaaS Factory</a></li>\n        <li><a href=\"#\">Contact</a></li>\n        <li><a href=\"#\">Mainpage</a></li>\n      </ul>\n      <p>&copy; 2022 AWS SaaS Factory. All rights reserved.</p>\n    </div>\n  </footer>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/views/landing/landing.component.scss",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\n // $color-red: #cc615f;\n $color-grey: #4b4b4b;\n $color-grey--light: #d3d3d3;\n $btn-amzn: #ec7211;\n\n $color-primary: #232f3e;\n $bp-s: 65.75em; //700px;\n $bp-xs: 34.375em; //550px;\n\n @import url(//fonts.googleapis.com/css?family=Open+Sans:400,700,800,300);\n\n *{\n   box-sizing: border-box;\n }\n html{\n   width: 100%;\n   height:100%;\n   margin: 0;\n   padding: 0;\n }\n body{\n   width: 100%;\n   height:100%;\n   font-family: 'Open Sans','Helvetica Neue',Helvetica, sans-serif;\n   font-size: 100%;\n   line-height: 1.45;\n   color: #141414;\n }\n\n a{\n   text-decoration: none;\n\n   &:hover{\n     text-decoration: none;\n   }\n }\n\n img{\n   max-width: 100%;\n }\n\n .btn{\n   display: inline-block;\n   margin: 1rem 0;\n   color: white;\n   font-weight: 500;\n   font-size: 1.3rem;\n   background: $btn-amzn;\n   letter-spacing: .02em;\n   border: none;\n   border-radius: 5px;\n   padding: .8rem 1rem .9rem;\n   text-shadow: 0 1px rgba(black,.3);\n   box-shadow: 0 0 2px rgba(black,.2);\n\n   @media (max-width: $bp-s){\n     padding: .5rem .7rem .6rem;\n     font-size: 1rem;\n   }\n\n   &:hover{\n     background: lighten($btn-amzn,5%);\n     color: #fff;\n   }\n   &:focus,\n   &:active,\n   &:focus:active{\n     background: darken($btn-amzn,5%);\n     border-color: darken($btn-amzn,5%);\n     box-shadow: 0 2px 5px 0 rgba(black,.5) inset;\n   }\n }\n\n .container{\n   margin: 0 auto;\n   width: 90%;\n   max-width: 900px;\n }\n\n\n header{\n   color: white;\n   background: #232f3e;\n   padding: 10rem 0;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   h1{\n     font-size: 3rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 2rem;\n     }\n   }\n   h2{\n     font-weight: 300;\n     font-size: 1.5rem;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n }\n\n section{\n   background: #fff;\n   color: $color-grey;\n   padding: 3.5rem 0;\n\n   @media (max-width: $bp-s){\n     padding: 2rem 0;\n   }\n\n   &.section--primary{\n     background: $color-primary;\n     color: #fff;\n   }\n   &.section--primary--alt{\n     background: desaturate(lighten($color-primary,15%),10%);\n     color: #fff;\n   }\n   &.section--primary--light{\n     background: rgba($color-primary,.1);\n   }\n\n   &.section--grey{\n     background: $color-grey;\n     color: #fff;\n   }\n   &.section--grey--light{\n     background: $color-grey--light;\n     color: #fff;\n   }\n\n   h3{\n     text-align: center;\n     font-size: 2rem;\n     font-weight: 300;\n     margin: 0 0 1rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n     }\n   }\n\n   li{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n   p{\n     font-size: 1.2rem;\n     font-weight: 300;\n   }\n }\n .col{\n   margin: 0 1.5%;\n   display: inline-block;\n   vertical-align: top;\n }\n .col-7{\n   @extend .col;\n   width: 64%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-3{\n   @extend .col;\n   width: 29%;\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n   }\n }\n .col-5{\n   @extend .col;\n   width: 30%;\n\n   @media (max-width: $bp-xs){\n     width: 60%;\n     margin: 0;\n   }\n }\n\n .details{\n   text-align: left;\n\n   h3{\n     font-size: 2rem;\n     text-align: left;\n   }\n }\n\n .features{\n   text-align: center;\n   padding: 1rem;\n\n   &:hover{\n     background: rgba(white,.1);\n   }\n\n   @media (max-width: $bp-s){\n     width: 100%;\n     margin: 0;\n     text-align: left;\n     border-bottom: 1px solid rgba(white,.2);\n\n     &:last-child{\n       border: none;\n     }\n   }\n\n   i{\n     font-size: 4rem;\n     margin: 0 0 2rem 0;\n\n     @media (max-width: $bp-s){\n       font-size: 1.5rem;\n       width: 2rem;\n       text-align: center;\n       margin: 0 0 1rem 0;\n       float: left;\n     }\n   }\n\n   p{\n     margin: 0 0 1rem 0;\n     font-size: 1rem;\n\n     @media (max-width: $bp-s){\n       margin-left: 3rem;\n     }\n   }\n }\n\n blockquote{\n   position: relative;\n   margin: 0;\n   padding: 0;\n   text-align: center;\n\n   &:before{\n     display: inline-block;\n     color: $color-primary;\n     font-size: 2rem;\n     content: '\\201C';\n   }\n\n   p{\n     margin: 0;\n     display: inline;\n     font-size: 1.5rem;\n\n     @media (max-width: $bp-s){\n       font-size: 1.2rem;\n     }\n   }\n\n   cite{\n     font-size: 1rem;\n     display: block;\n     margin: .5rem 0 0 1.2rem;\n\n     @media (max-width: $bp-s){\n       font-size: .8rem;\n     }\n\n     &:before{\n       content: '–';\n     }\n   }\n }\n\n footer{\n   background: $color-primary;\n   color: #fff;\n   padding: 2rem 0;\n   text-align: center;\n   font-size: .8rem;\n   color: rgba(white,.4);\n\n   ul{\n     margin: 0;\n     padding: 0;\n     list-style: none;\n\n     li{\n       display: inline-block;\n\n       a{\n         display: block;\n         padding: .4rem .7rem;\n         font-size: .9rem;\n         text-decoration: none;\n         color: rgba(white,.7);\n\n         &:hover{\n           color: white;\n         }\n       }\n     }\n   }\n }\n\n .text--center{\n   text-align: center;\n }\n .text--left{\n   text-align: left;\n }\n .bg-image{\n   background: $color-primary;\n   text-align: center;\n   position: relative;\n   z-index: 1;\n   overflow: hidden;\n   //text-shadow: 2px 0 5px black;\n\n   &:before{\n     content: '';\n     display: block;\n     position: absolute;\n     top: 0;\n     bottom: 0;\n     left: 0;\n     right: 0;\n     width: 100%;\n     height: 100%;\n     z-index: -1;\n     background-size: 100%;\n     background-attachment: fixed;\n     filter: blur(3px);\n     opacity: .8;\n     transform: scale(1.1);\n   }\n\n   &.bg-image-2:before{\n     opacity: .6;\n     background-position: center center;\n   }\n }\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/views/landing/landing.component.ts",
    "content": "/*\n * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this\n * software and associated documentation files (the \"Software\"), to deal in the Software\n * without restriction, including without limitation the rights to use, copy, modify,\n * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */\nimport { Component, OnInit } from '@angular/core';\nimport { Router } from '@angular/router';\n\n@Component({\n  selector: 'app-landing',\n  templateUrl: './landing.component.html',\n  styleUrls: ['./landing.component.scss'],\n})\nexport class LandingComponent implements OnInit {\n  constructor(private router: Router) {}\n\n  ngOnInit() {}\n\n  register() {\n    this.router.navigate(['register']);\n    return false;\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/views/register/register.component.html",
    "content": "<div fxLayout=\"column\" class=\"tenant-form\">\n  <form [formGroup]=\"tenantForm\" class=\"wide-form\">\n    <mat-card class=\"card\">\n      <mat-card-title>Provision a new Tenant</mat-card-title>\n      <mat-card-subtitle>\n        Login details will be sent to the email provided.\n      </mat-card-subtitle>\n      <mat-card-content>\n        <mat-form-field class=\"full-width\" appearance=\"fill\">\n          <mat-label>Name</mat-label>\n          <input\n            matInput\n            placeholder=\"tenant1\"\n            formControlName=\"tenantName\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Email</mat-label>\n          <input\n            matInput\n            placeholder=\"jsmith@example.com\"\n            formControlName=\"tenantEmail\"\n            required\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Phone</mat-label>\n          <input\n            matInput\n            placeholder=\"1234567890\"\n            formControlName=\"tenantPhone\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Address</mat-label>\n          <input\n            matInput\n            placeholder=\"\"\n            formControlName=\"tenantAddress\"\n          />\n        </mat-form-field>\n        <mat-form-field appearance=\"fill\">\n          <mat-label>Plan</mat-label>\n          <select matNativeControl required formControlName=\"tenantTier\">\n            <option>Select one...</option>\n            <option value=\"Basic\">Basic</option>\n            <option value=\"Standard\">Standard</option>\n            <option value=\"Premium\">Premium</option>\n            <option value=\"Platinum\">Platinum</option>\n          </select>\n        </mat-form-field>\n        <mat-card-footer>\n          <div class=\"button-panel\">\n            <div *ngIf=\"submitting\">\n              <mat-progress-spinner\n                color=\"primary\"\n                mode=\"indeterminate\"\n                diameter=\"15\"\n              >\n              </mat-progress-spinner>\n            </div>\n            <div *ngIf=\"!submitting\">\n                <button\n                mat-raised-button\n                color=\"primary\"\n                (click)=\"submit()\"\n                [disabled]=\"!tenantForm.valid || submitting\"\n                >\n                Submit\n              </button>\n              <button mat-raised-button color=\"warn\" [routerLink]=\"['/landing']\">\n                Cancel\n              </button>\n            </div>\n          </div>\n        </mat-card-footer>\n      </mat-card-content>\n    </mat-card>\n  </form>\n</div>\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/views/register/register.component.scss",
    "content": ".card {\n  margin: 20px;\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  width: fit-content;\n}\n\n.tenant-form {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.wide-form {\n  min-width: 150px;\n  max-width: 500px;\n  width: 100%;\n}\n\n.full-width {\n  width: 100%;\n}\n\n.mat-form-field {\n  width: 100%;\n}\n\n.button-panel {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin-bottom: 8px;\n}\n\nbutton {\n  margin: 4px;\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/app/views/register/register.component.ts",
    "content": "import { MatSnackBar } from '@angular/material/snack-bar';\nimport { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { HttpClient } from '@angular/common/http';\nimport { environment } from 'src/environments/environment';\n\n@Component({\n  selector: 'app-register',\n  templateUrl: './register.component.html',\n  styleUrls: ['./register.component.scss'],\n})\nexport class RegisterComponent implements OnInit {\n  submitting: boolean = false;\n  tenantForm: FormGroup = this.fb.group({\n    tenantName: ['', [Validators.required]],\n    tenantEmail: ['', [Validators.email, Validators.required]],\n    tenantTier: ['', [Validators.required]],\n    tenantPhone: [''],\n    tenantAddress: [''],\n  });\n\n  constructor(\n    private fb: FormBuilder,\n    private _snackBar: MatSnackBar,\n    private http: HttpClient\n  ) {}\n\n  ngOnInit(): void {}\n\n  openErrorMessageSnackBar(errorMessage: string) {\n    this._snackBar.open(errorMessage, 'Dismiss', {\n      duration: 4 * 1000, // seconds\n    });\n  }\n\n  submit() {\n    this.submitting = true;\n    this.tenantForm.disable();\n    const tenant = {\n      ...this.tenantForm.value,\n    };\n    this.http\n      .post(`${environment.apiGatewayUrl}/registration`, tenant)\n      .subscribe({\n        next: () => {\n          this.openErrorMessageSnackBar('Successfully created new tenant!');\n          this.tenantForm.reset();\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n        error: (err) => {\n          this.openErrorMessageSnackBar('An unexpected error occurred!');\n          console.error(err);\n          this.tenantForm.enable();\n          this.submitting = false;\n        },\n      });\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/assets/.gitkeep",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Landing/src/custom-theme.scss",
    "content": "\n// Custom Theming for Angular Material\n// For more information: https://material.angular.io/guide/theming\n@use '@angular/material' as mat;\n// Plus imports for other components in your app.\n\n// Include the common styles for Angular Material. We include this here so that you only\n// have to load a single css file for Angular Material in your app.\n// Be sure that you only ever include this mixin once!\n@include mat.core();\n\n// Define the palettes for your theme using the Material Design palettes available in palette.scss\n// (imported above). For each palette, you can optionally specify a default, lighter, and darker\n// hue. Available color palettes: https://material.io/design/color/\n$dashboard-primary: mat.define-palette(mat.$indigo-palette);\n$dashboard-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);\n\n// The warn palette is optional (defaults to red).\n$dashboard-warn: mat.define-palette(mat.$red-palette);\n\n// Create the theme object. A theme consists of configurations for individual\n// theming systems such as \"color\" or \"typography\".\n$dashboard-theme: mat.define-light-theme((\n  color: (\n    primary: $dashboard-primary,\n    accent: $dashboard-accent,\n    warn: $dashboard-warn,\n  )\n));\n\n// Include theme styles for core and each component used in your app.\n// Alternatively, you can import and @include the theme mixins for each component\n// that you are using.\n@include mat.all-component-themes($dashboard-theme);\n\n\nhtml, body { height: 100%; }\nbody { margin: 0; font-family: Roboto, \"Helvetica Neue\", sans-serif; }\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/environments/environment.prod.ts",
    "content": "export const environment = {\n  production: true,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/environments/environment.ts",
    "content": "export const environment = {\n  production: false,\n  apiGatewayUrl: 'https://duobieudcl.execute-api.us-west-2.amazonaws.com/prod',\n};\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix: -->\n    <script>\n      if (global === undefined) {\n        var global = window;\n      }\n    </script>\n    <!-- https://github.com/aws/aws-amplify/issues/678 fix end-->\n    <meta charset=\"utf-8\" />\n    <title>Landing</title>\n    <base href=\"./\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" />\n    <link\n      href=\"https://fonts.googleapis.com/icon?family=Material+Icons\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/main.ts",
    "content": "import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/polyfills.ts",
    "content": "/**\n * This file includes polyfills needed by Angular and is loaded before the app.\n * You can add your own extra polyfills to this file.\n *\n * This file is divided into 2 sections:\n *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.\n *   2. Application imports. Files imported after ZoneJS that should be loaded before your main\n *      file.\n *\n * The current setup is for so-called \"evergreen\" browsers; the last versions of browsers that\n * automatically update themselves. This includes recent versions of Safari, Chrome (including\n * Opera), Edge on the desktop, and iOS and Chrome on mobile.\n *\n * Learn more in https://angular.io/guide/browser-support\n */\n\n/***************************************************************************************************\n * BROWSER POLYFILLS\n */\n\n/**\n * By default, zone.js will patch all possible macroTask and DomEvents\n * user can disable parts of macroTask/DomEvents patch by setting following flags\n * because those flags need to be set before `zone.js` being loaded, and webpack\n * will put import in the top of bundle, so user need to create a separate file\n * in this directory (for example: zone-flags.ts), and put the following flags\n * into that file, and then add the following code before importing zone.js.\n * import './zone-flags';\n *\n * The flags allowed in zone-flags.ts are listed here.\n *\n * The following flags will work for all browsers.\n *\n * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame\n * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick\n * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames\n *\n *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js\n *  with the following flag, it will bypass `zone.js` patch for IE/Edge\n *\n *  (window as any).__Zone_enable_cross_context_check = true;\n *\n */\n\n/***************************************************************************************************\n * Zone JS is required by default for Angular itself.\n */\nimport 'zone.js';  // Included with Angular CLI.\n\n\n/***************************************************************************************************\n * APPLICATION IMPORTS\n */\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/styles/_variables.scss",
    "content": "$link-color: #673ab7; // 1\n$label-margin-bottom: 0; // 2\n$grid-breakpoints: (\n  xs: 0,\n  // handset portrait (small, medium, large) | handset landscape (small)\n  sm: 600px,\n  // handset landscape (medium, large) | tablet portrait (small, large)\n  md: 960px,\n  // tablet landscape (small, large)\n  lg: 1280px,\n  // laptops and desktops\n  xl: 1600px // large desktops,\n);\n\n$container-max-widths: (\n  sm: 600px,\n  md: 960px,\n  lg: 1280px,\n  xl: 1600px,\n);\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/styles/reset.scss",
    "content": "a {\n  &.mat-button,\n  &.mat-raised-button,\n  &.mat-fab,\n  &.mat-mini-fab,\n  &.mat-list-item {\n    &:hover {\n      color: currentColor;\n    }\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/src/styles.scss",
    "content": ""
  },
  {
    "path": "Solution/Lab6/client/Landing/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\ndeclare const require: {\n  context(path: string, deep?: boolean, filter?: RegExp): {\n    <T>(id: string): T;\n    keys(): string[];\n  };\n};\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(),\n);\n\n// Then we find all the tests.\nconst context = require.context('./', true, /\\.spec\\.ts$/);\n// And load the modules.\ncontext.keys().forEach(context);\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/app\",\n    \"types\": []\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"downlevelIteration\": true,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"es2020\",\n    \"module\": \"es2020\",\n    \"lib\": [\"es2020\", \"dom\"]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": true,\n    \"skipLibCheck\": true\n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/client/Landing/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"files\": [\n    \"src/test.ts\",\n    \"src/polyfills.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "Solution/Lab6/scripts/deployment.sh",
    "content": "\n# During AWS hosted events using event engine tool \n# we pre-provision cloudfront and s3 buckets which hosts UI code. \n# So that it improves this labs total execution time. \n# Below code checks if cloudfront and s3 buckets are \n# pre-provisioned or not and then concludes if the workshop \n# is running in AWS hosted event through event engine tool or not.\nIS_RUNNING_IN_EVENT_ENGINE=false \nPREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  IS_RUNNING_IN_EVENT_ENGINE=true\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\nfi\n\necho \"server code is getting deployed\"\ncd ../server\nREGION=$(aws configure get region)\n\necho \"Validating server code using pylint\"\npython3 -m pylint -E -d E0401 $(find . -iname \"*.py\" -not -path \"./.aws-sam/*\" -not -path \"./TenantPipeline/node_modules/*\")\nif [[ $? -ne 0 ]]; then\n  echo \"****ERROR: Please fix above code errors and then rerun script!!****\"\n  exit 1\nfi\n\nsam build -t shared-template.yaml --use-container\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = true ]; then\n  sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE AdminUserPoolCallbackURLParameter=$ADMIN_SITE_URL TenantUserPoolCallbackURLParameter=$APP_SITE_URL\nelse\n  sam deploy --config-file shared-samconfig.toml --region=$REGION --parameter-overrides EventEngineParameter=$IS_RUNNING_IN_EVENT_ENGINE\nfi\n    \n\necho \"Pooled tenant server code is getting deployed\"\nREGION=$(aws configure get region)\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml --region=$REGION\ncd ../scripts\n\nif [ \"$IS_RUNNING_IN_EVENT_ENGINE\" = false ]; then\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\"\n  \n\n\n\n\n\n\n"
  },
  {
    "path": "Solution/Lab6/scripts/geturl.sh",
    "content": "PREPROVISIONED_ADMIN_SITE=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\nif [ ! -z \"$PREPROVISIONED_ADMIN_SITE\" ]; then\n  echo \"Workshop is running in WorkshopStudio\"\n  ADMIN_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-AdminAppSite'].Value\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-LandingApplicationSite'].Value\" --output text)\n  APP_SITE_URL=$(aws cloudformation list-exports --query \"Exports[?Name=='Serverless-SaaS-ApplicationSite'].Value\" --output text)\n\nelse\n\n  ADMIN_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='AdminAppSite'].OutputValue\" --output text)\n  LANDING_APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='LandingApplicationSite'].OutputValue\" --output text)\n  APP_SITE_URL=$(aws cloudformation describe-stacks --stack-name serverless-saas --query \"Stacks[0].Outputs[?OutputKey=='ApplicationSite'].OutputValue\" --output text)\nfi\n\necho \"Admin site URL: https://$ADMIN_SITE_URL\"\necho \"Landing site URL: https://$LANDING_APP_SITE_URL\"\necho \"App site URL: https://$APP_SITE_URL\""
  },
  {
    "path": "Solution/Lab6/scripts/test-basic-tier-throttling.sh",
    "content": "#!/bin/bash\nAPP_APIGATEWAYURL=$(aws cloudformation describe-stacks --stack-name stack-pooled --query \"Stacks[0].Outputs[?OutputKey=='TenantAPI'].OutputValue\" --output text)\n\nget_product() {\n  curl -X GET -H \"Authorization: Bearer $1\" -H \"Content-Type: application/json\" $APP_APIGATEWAYURL/products\n  echo \"\\n $2\"\n}\n\nfor i in $(seq 1 550)\ndo\n  get_product $1 $i &\ndone\nwait\necho \"All done\""
  },
  {
    "path": "Solution/Lab6/server/.gitignore",
    "content": ".aws-sam/\n.pytest_cache/\n.DS_Store\n.vscode/"
  },
  {
    "path": "Solution/Lab6/server/OrderService/order_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Order:\n    key=''\n    def __init__(self, shardId, orderId, orderName, orderProducts):\n        self.shardId = shardId\n        self.orderId = orderId\n        self.key = shardId + ':' +  orderId\n        self.orderName = orderName\n        self.orderProducts = orderProducts\n\nclass  OrderProduct:\n\n    def __init__(self, productId, price, quantity):\n        self.productId = productId\n        self.price = price\n        self.quantity = quantity\n\n\n\n"
  },
  {
    "path": "Solution/Lab6/server/OrderService/order_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport order_service_dal\nfrom decimal import Decimal\nfrom types import SimpleNamespace\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to get a order\")\n    params = event['pathParameters']\n    key = params['id']\n    logger.log_with_tenant_context(event, params)\n    order = order_service_dal.get_order(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a order\")\n    metrics_manager.record_metric(event, \"SingleOrderRequested\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef create_order(event, context):  \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    order = order_service_dal.create_order(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a order\")\n    metrics_manager.record_metric(event, \"OrderCreated\", \"Count\", 1)\n    return utils.generate_response(order)\n    \n@tracer.capture_lambda_handler\ndef update_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to update a order\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    order = order_service_dal.update_order(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a order\") \n    metrics_manager.record_metric(event, \"OrderUpdated\", \"Count\", 1)   \n    return utils.generate_response(order)\n\n@tracer.capture_lambda_handler\ndef delete_order(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a order\")\n    params = event['pathParameters']\n    key = params['id']\n    response = order_service_dal.delete_order(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a order\")\n    metrics_manager.record_metric(event, \"OrderDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the order\")\n\n@tracer.capture_lambda_handler\ndef get_orders(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all orders\")\n    response = order_service_dal.get_orders(event, tenantId)\n    metrics_manager.record_metric(event, \"OrdersRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all orders\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab6/server/OrderService/order_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nfrom order_models import Order\nimport json\nimport utils\nfrom types import SimpleNamespace\nimport logger\nimport random\nimport threading\nfrom boto3.dynamodb.conditions import Key\nimport metrics_manager\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['ORDER_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n \n\ndef get_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n\n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        response = table.get_item(Key={'shardId': shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a order', e)\n    else:\n        return order\n\ndef delete_order(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'orderId': orderId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a order', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_order(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    table = __get_dynamodb_table(event, dynamodb)\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n    \n    order = Order(shardId, str(uuid.uuid4()), payload.orderName, payload.orderProducts)\n\n    try:\n        response = table.put_item(Item={\n        'shardId':shardId,\n        'orderId': order.orderId, \n        'orderName': order.orderName,\n        'orderProducts': get_order_products_dict(order.orderProducts)\n        }, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a order', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return order\n\ndef update_order(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        orderId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, orderId)\n        order = Order(shardId, orderId,payload.orderName, payload.orderProducts)\n        response = table.update_item(Key={'shardId':order.shardId, 'orderId': order.orderId},\n        UpdateExpression=\"set orderName=:orderName, \"\n        +\"orderProducts=:orderProducts\",\n        ExpressionAttributeValues={\n            ':orderName': order.orderName,\n            ':orderProducts': get_order_products_dict(order.orderProducts)\n        },\n        ReturnValues=\"UPDATED_NEW\", ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a order', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return order\n\ndef get_orders(event, tenantId):\n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response = []\n\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(\"Error getting all orders\")\n        raise Exception('Error getting all orders', e) \n    else:\n        logger.info(\"Get orders succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            order = Order(item['shardId'], item['orderId'], item['orderName'], item['orderProducts'])\n            get_all_products_response.append(order)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):\n    \"\"\" Determine the table name based upo pooled vs silo model\n\n    Args:\n        event ([type]): [description]\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )        \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n\ndef get_order_products_dict(orderProducts):\n    orderProductList = []\n    for i in range(len(orderProducts)):\n        product = orderProducts[i]\n        orderProductList.append(vars(product))\n    return orderProductList    \n\n  \n\n"
  },
  {
    "path": "Solution/Lab6/server/OrderService/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab6/server/ProductService/product_models.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nclass Product:\n    key =''\n    def __init__(self, shardId, productId, sku, name, price, category):\n        self.shardId = shardId\n        self.productId = productId\n        self.key = shardId + ':' +  productId\n        self.sku = sku\n        self.name = name\n        self.price = price\n        self.category = category\n\nclass Category:\n    def __init__(self, id, name):\n        self.id = id\n        self.name = name\n                \n\n        \n\n               \n\n        \n"
  },
  {
    "path": "Solution/Lab6/server/ProductService/product_service.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\nimport logger\nimport metrics_manager\nimport product_service_dal\nfrom aws_lambda_powertools import Tracer\nfrom decimal import Decimal\nfrom types import SimpleNamespace\ntracer = Tracer()\n\n@tracer.capture_lambda_handler\ndef get_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get a product\")\n    params = event['pathParameters']\n    logger.log_with_tenant_context(event, params)\n    key = params['id']\n    logger.log_with_tenant_context(event, key)\n    product = product_service_dal.get_product(event, key)\n\n    logger.log_with_tenant_context(event, \"Request completed to get a product\")\n    metrics_manager.record_metric(event, \"SingleProductRequested\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef create_product(event, context):    \n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to create a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    product = product_service_dal.create_product(event, payload)\n    logger.log_with_tenant_context(event, \"Request completed to create a product\")\n    metrics_manager.record_metric(event, \"ProductCreated\", \"Count\", 1)\n    return utils.generate_response(product)\n    \n@tracer.capture_lambda_handler\ndef update_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to update a product\")\n    payload = json.loads(event['body'], object_hook=lambda d: SimpleNamespace(**d), parse_float=Decimal)\n    params = event['pathParameters']\n    key = params['id']\n    product = product_service_dal.update_product(event, payload, key)\n    logger.log_with_tenant_context(event, \"Request completed to update a product\") \n    metrics_manager.record_metric(event, \"ProductUpdated\", \"Count\", 1)   \n    return utils.generate_response(product)\n\n@tracer.capture_lambda_handler\ndef delete_product(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n\n    logger.log_with_tenant_context(event, \"Request received to delete a product\")\n    params = event['pathParameters']\n    key = params['id']\n    response = product_service_dal.delete_product(event, key)\n    logger.log_with_tenant_context(event, \"Request completed to delete a product\")\n    metrics_manager.record_metric(event, \"ProductDeleted\", \"Count\", 1)\n    return utils.create_success_response(\"Successfully deleted the product\")\n\n@tracer.capture_lambda_handler\ndef get_products(event, context):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n    tracer.put_annotation(key=\"TenantId\", value=tenantId)\n    \n    logger.log_with_tenant_context(event, \"Request received to get all products\")\n    response = product_service_dal.get_products(event, tenantId)\n    metrics_manager.record_metric(event, \"ProductsRetrieved\", \"Count\", len(response))\n    logger.log_with_tenant_context(event, \"Request completed to get all products\")\n    return utils.generate_response(response)\n\n  "
  },
  {
    "path": "Solution/Lab6/server/ProductService/product_service_dal.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom pprint import pprint\nimport os\nimport boto3\nfrom botocore.exceptions import ClientError\nimport uuid\nimport json\nimport logger\nimport random\nimport threading\nimport metrics_manager\n\nfrom product_models import Product\nfrom types import SimpleNamespace\nfrom boto3.dynamodb.conditions import Key\n\n\nis_pooled_deploy = os.environ['IS_POOLED_DEPLOY']\ntable_name = os.environ['PRODUCT_TABLE_NAME']\ndynamodb = None\n\nsuffix_start = 1 \nsuffix_end = 10\n\ndef get_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n        response = table.get_item(Key={'shardId': shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n        item = response['Item']\n        product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n\n        metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting a product', e)\n    else:\n        logger.info(\"GetItem succeeded:\"+ str(product))\n        return product\n\ndef delete_product(event, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        response = table.delete_item(Key={'shardId':shardId, 'productId': productId}, ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error deleting a product', e)\n    else:\n        logger.info(\"DeleteItem succeeded:\")\n        return response\n\n\ndef create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']    \n    table = __get_dynamodb_table(event, dynamodb)\n\n    \n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n    \n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,  \n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }, ReturnConsumedCapacity='TOTAL'\n        )\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\ndef update_product(event, payload, key):\n    table = __get_dynamodb_table(event, dynamodb)\n    \n    try:\n        shardId = key.split(\":\")[0]\n        productId = key.split(\":\")[1] \n        logger.log_with_tenant_context(event, shardId)\n        logger.log_with_tenant_context(event, productId)\n\n        product = Product(shardId,productId,payload.sku, payload.name, payload.price, payload.category)\n\n        response = table.update_item(Key={'shardId':product.shardId, 'productId': product.productId},\n        UpdateExpression=\"set sku=:sku, #n=:productName, price=:price, category=:category\",\n        ExpressionAttributeNames= {'#n':'name'},\n        ExpressionAttributeValues={\n            ':sku': product.sku,\n            ':productName': product.name,\n            ':price': product.price,\n            ':category': product.category\n        },\n        ReturnValues=\"UPDATED_NEW\", ReturnConsumedCapacity='TOTAL')\n\n        metrics_manager.record_metric(event, \"WriteCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error updating a product', e)\n    else:\n        logger.info(\"UpdateItem succeeded:\")\n        return product        \n\ndef get_products(event, tenantId):    \n    table = __get_dynamodb_table(event, dynamodb)\n    get_all_products_response =[]\n    try:\n        __query_all_partitions(tenantId,get_all_products_response, table, event)\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error getting all products', e)\n    else:\n        logger.info(\"Get products succeeded\")\n        return get_all_products_response\n\ndef __query_all_partitions(tenantId,get_all_products_response, table, event):\n    threads = []    \n    \n    for suffix in range(suffix_start, suffix_end):\n        partition_id = tenantId+'-'+str(suffix)\n        \n        thread = threading.Thread(target=__get_tenant_data, args=[partition_id, get_all_products_response, table, event])\n        threads.append(thread)\n        \n    # Start threads\n    for thread in threads:\n        thread.start()\n    # Ensure all threads are finished\n    for thread in threads:\n        thread.join()\n           \ndef __get_tenant_data(partition_id, get_all_products_response, table, event):    \n    logger.info(partition_id)\n    response = table.query(KeyConditionExpression=Key('shardId').eq(partition_id), ReturnConsumedCapacity='TOTAL')    \n    if (len(response['Items']) > 0):\n        for item in response['Items']:\n            product = Product(item['shardId'], item['productId'], item['sku'], item['name'], item['price'], item['category'])\n            get_all_products_response.append(product)\n\n    metrics_manager.record_metric(event, \"ReadCapacityUnits\", \"Count\", response['ConsumedCapacity']['CapacityUnits'])        \n\ndef __get_dynamodb_table(event, dynamodb):    \n    if (is_pooled_deploy=='true'):\n        accesskey = event['requestContext']['authorizer']['accesskey']\n        secretkey = event['requestContext']['authorizer']['secretkey']\n        sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n        dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )       \n    else:\n        if not dynamodb:\n            dynamodb = boto3.resource('dynamodb')\n        \n    return dynamodb.Table(table_name)\n"
  },
  {
    "path": "Solution/Lab6/server/ProductService/requirements.txt",
    "content": "requests\npytest-mock\naws-lambda-powertools[Tracer,Logger,Metrics]\njsonpickle\naws_requests_auth"
  },
  {
    "path": "Solution/Lab6/server/README.md",
    "content": "sam build -t shared-template.yaml --use-container\nsam deploy --config-file shared-samconfig.toml\n\n\nsam build -t tenant-template.yaml --use-container\nsam deploy --config-file tenant-samconfig.toml\n\n"
  },
  {
    "path": "Solution/Lab6/server/Resources/requirements.txt",
    "content": "python-jose[cryptography]"
  },
  {
    "path": "Solution/Lab6/server/Resources/shared_service_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\napi_key_operation_user = os.environ['OPERATION_USERS_API_KEY']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user  \n        api_key = api_key_operation_user\n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']    \n        api_key = tenant_details['Item']['apiKey']    \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    #only tenant admin and system admin can do certain actions like create and disable users\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n        \n\n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.SHARED_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'apiKey': api_key,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    authResponse['usageIdentifierKey'] = api_key\n    \n    return authResponse\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab6/server/Resources/tenant_authorizer.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport re\nimport json\nimport os\nimport urllib.request\nimport boto3\nimport time\nimport logger\nfrom jose import jwk, jwt\nfrom jose.utils import base64url_decode\nimport auth_manager\nimport utils\n\nregion = os.environ['AWS_REGION']\nsts_client = boto3.client(\"sts\", region_name=region)\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\nuser_pool_operation_user = os.environ['OPERATION_USERS_USER_POOL']\napp_client_operation_user = os.environ['OPERATION_USERS_APP_CLIENT']\napi_key_operation_user = os.environ['OPERATION_USERS_API_KEY']\n\ndef lambda_handler(event, context):\n    \n    #get JWT token after Bearer from authorization\n    token = event['authorizationToken'].split(\" \")\n    if (token[0] != 'Bearer'):\n        raise Exception('Authorization header should have a format Bearer <JWT> Token')\n    jwt_bearer_token = token[1]\n    logger.info(\"Method ARN: \" + event['methodArn'])\n    \n    #only to get tenant id to get user pool info\n    unauthorized_claims = jwt.get_unverified_claims(jwt_bearer_token)\n    logger.info(unauthorized_claims)\n\n    if(auth_manager.isSaaSProvider(unauthorized_claims['custom:userRole'])):\n        userpool_id = user_pool_operation_user\n        appclient_id = app_client_operation_user   \n        api_key = api_key_operation_user  \n    else:\n        #get tenant user pool and app client to validate jwt token against\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': unauthorized_claims['custom:tenantId']\n            }\n        )\n        logger.info(tenant_details)\n        userpool_id = tenant_details['Item']['userPoolId']\n        appclient_id = tenant_details['Item']['appClientId']\n        apigateway_url = tenant_details['Item']['apiGatewayUrl']\n        api_key = tenant_details['Item']['apiKey']\n        \n\n    #get keys for tenant user pool to validate\n    keys_url = 'https://cognito-idp.{}.amazonaws.com/{}/.well-known/jwks.json'.format(region, userpool_id)\n    with urllib.request.urlopen(keys_url) as f:\n        response = f.read()\n    keys = json.loads(response.decode('utf-8'))['keys']\n\n    #authenticate against cognito user pool using the key\n    response = validateJWT(jwt_bearer_token, appclient_id, keys)\n    \n    #get authenticated claims\n    if (response == False):\n        logger.error('Unauthorized')\n        raise Exception('Unauthorized')\n    else:\n        logger.info(response)\n        principal_id = response[\"sub\"]\n        user_name = response[\"cognito:username\"]\n        tenant_id = response[\"custom:tenantId\"]\n        user_role = response[\"custom:userRole\"]\n    \n    \n    tmp = event['methodArn'].split(':')\n    api_gateway_arn_tmp = tmp[5].split('/')\n    aws_account_id = tmp[4]    \n    \n    policy = AuthPolicy(principal_id, aws_account_id)\n    policy.restApiId = api_gateway_arn_tmp[0]\n    policy.region = tmp[3]\n    policy.stage = api_gateway_arn_tmp[1]\n\n    if (auth_manager.isSaaSProvider(user_role) == False):\n        if (isTenantAuthorizedForThisAPI(apigateway_url, api_gateway_arn_tmp[0]) == False):\n            logger.error('Unauthorized')\n            raise Exception('Unauthorized')\n\n    #roles are not fine-grained enough to allow selectively\n    policy.allowAllMethods()        \n    \n    authResponse = policy.build()\n \n    #   Generate STS credentials to be used for FGAC\n    \n    #   Important Note: \n    #   We are generating STS token inside Authorizer to take advantage of the caching behavior of authorizer\n    #   Another option is to generate the STS token inside the lambda function itself, as mentioned in this blog post: https://aws.amazon.com/blogs/apn/isolating-saas-tenants-with-dynamically-generated-iam-policies/\n    #   Finally, you can also consider creating one Authorizer per microservice in cases where you want the IAM policy specific to that service \n    \n    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.BUSINESS_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n    \n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n    \n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'apiKey': api_key,\n        'userRole': user_role\n    }\n    \n    authResponse['context'] = context\n    authResponse['usageIdentifierKey'] = api_key\n    \n    return authResponse\n\ndef isTenantAuthorizedForThisAPI(apigateway_url, current_api_id):\n    if(apigateway_url.split('.')[0] != 'https://' + current_api_id):\n        return False\n    else:\n        return True\n\ndef validateJWT(token, app_client_id, keys):\n    # get the kid from the headers prior to verification\n    headers = jwt.get_unverified_headers(token)\n    kid = headers['kid']\n    # search for the kid in the downloaded public keys\n    key_index = -1\n    for i in range(len(keys)):\n        if kid == keys[i]['kid']:\n            key_index = i\n            break\n    if key_index == -1:\n        logger.info('Public key not found in jwks.json')\n        return False\n    # construct the public key\n    public_key = jwk.construct(keys[key_index])\n    # get the last two sections of the token,\n    # message and signature (encoded in base64)\n    message, encoded_signature = str(token).rsplit('.', 1)\n    # decode the signature\n    decoded_signature = base64url_decode(encoded_signature.encode('utf-8'))\n    # verify the signature\n    if not public_key.verify(message.encode(\"utf8\"), decoded_signature):\n        logger.info('Signature verification failed')\n        return False\n    logger.info('Signature successfully verified')\n    # since we passed the verification, we can now safely\n    # use the unverified claims\n    claims = jwt.get_unverified_claims(token)\n    # additionally we can verify the token expiration\n    if time.time() > claims['exp']:\n        logger.info('Token is expired')\n        return False\n    # and the Audience  (use claims['client_id'] if verifying an access token)\n    if claims['aud'] != app_client_id:\n        logger.info('Token was not issued for this audience')\n        return False\n    # now we can use the claims\n    logger.info(claims)\n    return claims\n\n\nclass HttpVerb:\n    GET     = \"GET\"\n    POST    = \"POST\"\n    PUT     = \"PUT\"\n    PATCH   = \"PATCH\"\n    HEAD    = \"HEAD\"\n    DELETE  = \"DELETE\"\n    OPTIONS = \"OPTIONS\"\n    ALL     = \"*\"\n\nclass AuthPolicy(object):\n    awsAccountId = \"\"\n    \"\"\"The AWS account id the policy will be generated for. This is used to create the method ARNs.\"\"\"\n    principalId = \"\"\n    \"\"\"The principal used for the policy, this should be a unique identifier for the end user.\"\"\"\n    version = \"2012-10-17\"\n    \"\"\"The policy version used for the evaluation. This should always be '2012-10-17'\"\"\"\n    pathRegex = \"^[/.a-zA-Z0-9-\\*]+$\"\n    \"\"\"The regular expression used to validate resource paths for the policy\"\"\"\n\n    \"\"\"these are the internal lists of allowed and denied methods. These are lists\n    of objects and each object has 2 properties: A resource ARN and a nullable\n    conditions statement.\n    the build method processes these lists and generates the approriate\n    statements for the final policy\"\"\"\n    allowMethods = []\n    denyMethods = []\n\n    restApiId = \"*\"\n    \"\"\"The API Gateway API id. By default this is set to '*'\"\"\"\n    region = \"*\"\n    \"\"\"The region where the API is deployed. By default this is set to '*'\"\"\"\n    stage = \"*\"\n    \"\"\"The name of the stage used in the policy. By default this is set to '*'\"\"\"\n\n    def __init__(self, principal, awsAccountId):\n        self.awsAccountId = awsAccountId\n        self.principalId = principal\n        self.allowMethods = []\n        self.denyMethods = []\n\n    def _addMethod(self, effect, verb, resource, conditions):\n        \"\"\"Adds a method to the internal lists of allowed or denied methods. Each object in\n        the internal list contains a resource ARN and a condition statement. The condition\n        statement can be null.\"\"\"\n        if verb != \"*\" and not hasattr(HttpVerb, verb):\n            raise NameError(\"Invalid HTTP verb \" + verb + \". Allowed verbs in HttpVerb class\")\n        resourcePattern = re.compile(self.pathRegex)\n        if not resourcePattern.match(resource):\n            raise NameError(\"Invalid resource path: \" + resource + \". Path should match \" + self.pathRegex)\n\n        if resource[:1] == \"/\":\n            resource = resource[1:]\n\n        resourceArn = (\"arn:aws:execute-api:\" +\n            self.region + \":\" +\n            self.awsAccountId + \":\" +\n            self.restApiId + \"/\" +\n            self.stage + \"/\" +\n            verb + \"/\" +\n            resource)\n\n        if effect.lower() == \"allow\":\n            self.allowMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n        elif effect.lower() == \"deny\":\n            self.denyMethods.append({\n                'resourceArn' : resourceArn,\n                'conditions' : conditions\n            })\n\n    def _getEmptyStatement(self, effect):\n        \"\"\"Returns an empty statement object prepopulated with the correct action and the\n        desired effect.\"\"\"\n        statement = {\n            'Action': 'execute-api:Invoke',\n            'Effect': effect[:1].upper() + effect[1:].lower(),\n            'Resource': []\n        }\n\n        return statement\n\n    def _getStatementForEffect(self, effect, methods):\n        \"\"\"This function loops over an array of objects containing a resourceArn and\n        conditions statement and generates the array of statements for the policy.\"\"\"\n        statements = []\n\n        if len(methods) > 0:\n            statement = self._getEmptyStatement(effect)\n\n            for curMethod in methods:\n                if curMethod['conditions'] is None or len(curMethod['conditions']) == 0:\n                    statement['Resource'].append(curMethod['resourceArn'])\n                else:\n                    conditionalStatement = self._getEmptyStatement(effect)\n                    conditionalStatement['Resource'].append(curMethod['resourceArn'])\n                    conditionalStatement['Condition'] = curMethod['conditions']\n                    statements.append(conditionalStatement)\n\n            statements.append(statement)\n\n        return statements\n\n    def allowAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to authorize access to all methods of an API\"\"\"\n        self._addMethod(\"Allow\", HttpVerb.ALL, \"*\", [])\n\n    def denyAllMethods(self):\n        \"\"\"Adds a '*' allow to the policy to deny access to all methods of an API\"\"\"\n        self._addMethod(\"Deny\", HttpVerb.ALL, \"*\", [])\n\n    def allowMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods for the policy\"\"\"\n        self._addMethod(\"Allow\", verb, resource, [])\n\n    def denyMethod(self, verb, resource):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods for the policy\"\"\"\n        self._addMethod(\"Deny\", verb, resource, [])\n\n    def allowMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of allowed\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Allow\", verb, resource, conditions)\n\n    def denyMethodWithConditions(self, verb, resource, conditions):\n        \"\"\"Adds an API Gateway method (Http verb + Resource path) to the list of denied\n        methods and includes a condition for the policy statement. More on AWS policy\n        conditions here: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition\"\"\"\n        self._addMethod(\"Deny\", verb, resource, conditions)\n\n    def build(self):\n        \"\"\"Generates the policy document based on the internal lists of allowed and denied\n        conditions. This will generate a policy with two main statements for the effect:\n        one statement for Allow and one statement for Deny.\n        Methods that includes conditions will have their own statement in the policy.\"\"\"\n        if ((self.allowMethods is None or len(self.allowMethods) == 0) and\n            (self.denyMethods is None or len(self.denyMethods) == 0)):\n            raise NameError(\"No statements defined for the policy\")\n\n        policy = {\n            'principalId' : self.principalId,\n            'policyDocument' : {\n                'Version' : self.version,\n                'Statement' : []\n            }\n        }\n\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Allow\", self.allowMethods))\n        policy['policyDocument']['Statement'].extend(self._getStatementForEffect(\"Deny\", self.denyMethods))\n\n        return policy"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/events/env.json",
    "content": "{\n    \"CreateTenantAdminUserFunction\": {\n        \"DEFAULT_USER_POOL_ID\": \"us-west-2_uliP336sh\",  \n        \"LOG_LEVEL\": \"INFO\"\n    },\n    \"RegisterTenantFunction\": {\n        \"CREATE_TENANT_ADMIN_USER_FUNCTION\": \"arn:aws:lambda:us-west-2:779954754415:function:serverless-saas-admin-CreateTenantAdminUserFunctio-D5K1EGEMC4QG\",\n        \"CREATE_TENANT_FUNCTION\": \"arn:aws:lambda:us-west-2:779954754415:function:serverless-saas-admin-CreateTenantFunction-13GSSCVNKTV71\",\n        \"PREMIUM_TIER_API_KEY\": \"yy\",\n        \"STANDARD_TIER_API_KEY\": \"xx\",\n        \"BASIC_TIER_API_KEY\": \"zz\",\n        \"LOG_LEVEL\": \"DEBUG\"\n    }\n}"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/events/tenant-registration.json",
    "content": "\n{\n    \"body\": \"{\\\"tenantName\\\": \\\"First Tenant\\\", \\\"tenantAddress\\\": \\\"123 St\\\", \\\"tenantEmail\\\": \\\"a@a.com\\\", \\\"tenantPhone\\\": \\\"1234567890\\\", \\\"tenantTier\\\": \\\"Standard\\\",  \\\"tenantId\\\": \\\"62d1f8793b5e11eb876\\\", \\\"apiKey\\\": \\\"62d2062a3b5e11eb839457148f07121x\\\"}\"}"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/events/update_users_apikey_by_tenant.json",
    "content": "\n{\n    \"body\": \"{\\\"tenantId\\\": \\\"94d1bc6976ef11eb80cb81ceaed21f3f\\\", \\\"userPoolId\\\": \\\"us-west-2_vvN1hB5pd\\\", \\\"apiKey\\\": \\\"6db2bdc2-6d96-11eb-a56f-38f9d33cfd0f\\\"}\"\n}"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/events/user-management.json",
    "content": "\n{\n    \"tenantName\": \"First Tenant\", \n    \"tenantAddress\": \"123 St\", \n    \"tenantEmail\": \"a@a.com\", \n    \"tenantPhone\": \"1234567890\", \n    \"tenantTier\": \"Standard\", \n    \"dedicatedTenancy\": \"true\", \n    \"tenantId\": \"62d1f8793b5e11eb876\", \n    \"apiKey\": \"62d2062a3b5e11eb839457148f07121x\"\n}"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/requirements.txt",
    "content": "requests\naws_requests_auth"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/tenant-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport os\nimport json\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nimport urllib.parse\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport metrics_manager\nimport auth_manager\nimport requests\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\n\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\n\nregion = os.environ['AWS_REGION']\n\n#This method has been locked down to be only\ndef create_tenant(event, context):\n    api_gateway_url = ''       \n    tenant_details = json.loads(event['body'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    table_system_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n    try:          \n        # for pooled tenants the apigateway url is saving in settings during stack creation\n        # update from there during tenant creation\n        if(tenant_details['dedicatedTenancy'].lower()!= 'true'):\n            settings_response = table_system_settings.get_item(\n                Key={\n                    'settingName': 'apiGatewayUrl-Pooled'\n                } \n            )\n            api_gateway_url = settings_response['Item']['settingValue']\n\n        response = table_tenant_details.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'tenantName' : tenant_details['tenantName'],\n                    'tenantAddress': tenant_details['tenantAddress'],\n                    'tenantEmail': tenant_details['tenantEmail'],\n                    'tenantPhone': tenant_details['tenantPhone'],\n                    'tenantTier': tenant_details['tenantTier'],\n                    'apiKey': tenant_details['apiKey'],\n                    'userPoolId': tenant_details['userPoolId'],                 \n                    'appClientId': tenant_details['appClientId'],\n                    'dedicatedTenancy': tenant_details['dedicatedTenancy'],\n                    'isActive': True,\n                    'apiGatewayUrl': api_gateway_url\n                }\n            )                    \n\n    except Exception as e:\n        raise Exception('Error creating a new tenant', e)\n    else:\n        return utils.create_success_response(\"Tenant Created\")\n\ndef get_tenants(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n\n    try:\n        response = table_tenant_details.scan()\n    except Exception as e:\n        raise Exception('Error getting all tenants', e)\n    else:\n        return utils.generate_response(response['Items'])    \n\n\n@tracer.capture_lambda_handler\ndef update_tenant(event, context):\n    \n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_details = json.loads(event['body'])\n    tenant_id = event['pathParameters']['tenantid']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        exiting_tenant_details = table_tenant_details.get_item(\n                Key={\n                    'tenantId': tenant_id,\n                }    \n            )             \n\n        if (exiting_tenant_details['Item']['tenantTier'].upper() != tenant_details['tenantTier'].upper()):\n            api_key = __getApiKey(tenant_details['tenantTier'])\n        else:            \n            api_key = exiting_tenant_details['Item']['apiKey']\n\n        response_update = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set tenantName = :tenantName, tenantAddress = :tenantAddress, tenantEmail = :tenantEmail, tenantPhone = :tenantPhone, tenantTier=:tenantTier, apiKey=:apiKey\",\n            ExpressionAttributeValues={\n                    ':tenantName' : tenant_details['tenantName'],\n                    ':tenantAddress': tenant_details['tenantAddress'],\n                    ':tenantEmail': tenant_details['tenantEmail'],\n                    ':tenantPhone': tenant_details['tenantPhone'],\n                    ':tenantTier': tenant_details['tenantTier'],\n                    ':apiKey': api_key\n                },\n            ReturnValues=\"UPDATED_NEW\"\n            )             \n            \n        \n        logger.log_with_tenant_context(event, response_update)     \n\n        logger.log_with_tenant_context(event, \"Request completed to update tenant\")\n        return utils.create_success_response(\"Tenant Updated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update tenant!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get tenant details\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        tenant_details = table_tenant_details.get_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            AttributesToGet=[\n                'tenantName',\n                'tenantAddress',\n                'tenantEmail',\n                'tenantPhone'\n            ]    \n        )             \n        item = tenant_details['Item']\n        tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n        logger.log_with_tenant_context(event, tenant_info)\n        \n        logger.log_with_tenant_context(event, \"Request completed to get tenant details\")\n        return utils.create_success_response(tenant_info.__dict__)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\ndef deactivate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_disable_users = os.environ['DISABLE_USERS_BY_TENANT']\n    url_deprovision_tenant = os.environ['DEPROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to deactivate tenant\")\n\n    if ((auth_manager.isTenantAdmin(user_role) and tenant_id == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': False\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            update_user_response = __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, url_deprovision_tenant)\n\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_disable_users(update_details, headers, auth, host, stage_name, url_disable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to deactivate tenant\")\n        return utils.create_success_response(\"Tenant Deactivated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can deactivate tenant!\")        \n        return utils.create_unauthorized_response()    \n\n@tracer.capture_lambda_handler\ndef activate_tenant(event, context):\n    table_tenant_details = __getTenantManagementTable(event)\n    \n    url_enable_users = os.environ['ENABLE_USERS_BY_TENANT']\n    url_provision_tenant = os.environ['PROVISION_TENANT']\n    stage_name = event['requestContext']['stage']\n    host = event['headers']['Host']\n    auth = utils.get_auth(host, region)\n    headers = utils.get_headers(event)\n\n    requesting_tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    \n    tenant_id = event['pathParameters']['tenantid']\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to activate tenant\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        response = table_tenant_details.update_item(\n            Key={\n                'tenantId': tenant_id,\n            },\n            UpdateExpression=\"set isActive = :isActive\",\n            ExpressionAttributeValues={\n                    ':isActive': True\n                },\n            ReturnValues=\"ALL_NEW\"\n            )             \n        \n        logger.log_with_tenant_context(event, response)\n\n        if (response[\"Attributes\"][\"dedicatedTenancy\"].upper() == \"TRUE\"):\n            update_details = {}\n            update_details['tenantId'] = tenant_id            \n            provision_response = __invoke_provision_tenant(update_details, headers, auth, host, stage_name, url_provision_tenant)\n            logger.log_with_tenant_context(event, provision_response)\n        \n        update_details = {}\n        update_details['userPoolId'] = response[\"Attributes\"]['userPoolId']\n        update_details['tenantId'] = tenant_id\n        update_details['requestingTenantId'] = requesting_tenant_id\n        update_details['userRole'] = user_role\n        update_user_response = __invoke_enable_users(update_details, headers, auth, host, stage_name, url_enable_users)\n        logger.log_with_tenant_context(event, update_user_response)\n\n        logger.log_with_tenant_context(event, \"Request completed to activate tenant\")\n        return utils.create_success_response(\"Tenant Activated\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only system admin can activate tenant!\")        \n        return utils.create_unauthorized_response()    \n\ndef load_tenant_config(event, context):\n    params = event['pathParameters']\n    tenantName = urllib.parse.unquote(params['tenantname'])\n\n    dynamodb = boto3.resource('dynamodb')\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    try:\n        response = table_tenant_details.query(\n            IndexName=\"ServerlessSaas-TenantConfig\",\n            KeyConditionExpression=Key('tenantName').eq(tenantName),\n            ProjectionExpression=\"userPoolId, appClientId, apiGatewayUrl\"\n        ) \n    except Exception as e:\n        raise Exception('Error getting tenant config', e)\n    else:\n        if (response['Count'] == 0):\n            return utils.create_notfound_response(\"Tenant not found.\"+\n            \"Please enter exact tenant name used during tenant registration.\")\n        else:\n            return utils.generate_response(response['Items'][0])        \n\ndef __invoke_disable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while disabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while disabling users for the tenant')\n        raise Exception('Error occured while disabling users for the tenant', e) \n    else:\n        return \"Success invoking disable users\"\n\ndef __invoke_deprovision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url + update_details['tenantId']])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while deprovisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while deprovisioning tenant')\n        raise Exception('Error occured while deprovisioning tenant', e) \n    else:\n        return \"Success invoking deprovision tenant\"\n\ndef __invoke_enable_users(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.put(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while enabling users for the tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while enabling users for the tenant')\n        raise Exception('Error occured while enabling users for the tenant', e) \n    else:\n        return \"Success invoking enable users\"\n\ndef __invoke_provision_tenant(update_details, headers, auth, host, stage_name, invoke_url):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, invoke_url])\n        response = requests.post(url, data=json.dumps(update_details), auth=auth, headers=headers) \n        \n        logger.info(response.status_code)\n        if (int(response.status_code) != int(utils.StatusCodes.SUCCESS.value)):\n            raise Exception('Error occured while provisioning tenant')     \n        \n    except Exception as e:\n        logger.error('Error occured while provisioning tenant')\n        raise Exception('Error occured while provisioning tenant', e) \n    else:\n        return \"Success invoking provision tenant\"\n\ndef __getApiKey(tenant_tier):\n    if (tenant_tier.upper() == utils.TenantTier.PLATINUM.value.upper()):\n        return os.environ['PLATINUM_TIER_API_KEY']\n    elif (tenant_tier.upper() == utils.TenantTier.PREMIUM.value.upper()):\n        return os.environ['PREMIUM_TIER_API_KEY']\n    elif (tenant_tier.upper() == utils.TenantTier.STANDARD.value.upper()):\n        return os.environ['STANDARD_TIER_API_KEY']\n    elif (tenant_tier.upper() == utils.TenantTier.BASIC.value.upper()):\n        return os.environ['BASIC_TIER_API_KEY']\n        \ndef __getTenantManagementTable(event):\n    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']    \n    dynamodb = boto3.resource('dynamodb', aws_access_key_id=accesskey, aws_secret_access_key=secretkey, aws_session_token=sessiontoken)\n    table_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')#TODO: read table names from env vars\n    \n    return table_tenant_details\n\nclass TenantInfo:\n    def __init__(self, tenant_name, tenant_address, tenant_email, tenant_phone):\n        self.tenant_name = tenant_name\n        self.tenant_address = tenant_address\n        self.tenant_email = tenant_email\n        self.tenant_phone = tenant_phone\n\n   "
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/tenant-provisioning.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport utils\nfrom botocore.exceptions import ClientError\nimport logger\nimport os\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\ntenant_stack_mapping_table_name = os.environ['TENANT_STACK_MAPPING_TABLE_NAME']\n\ndynamodb = boto3.resource('dynamodb')\ncodepipeline = boto3.client('codepipeline')\ncloudformation = boto3.client('cloudformation')\ntable_tenant_stack_mapping = dynamodb.Table(tenant_stack_mapping_table_name)\n\nstack_name = 'stack-{0}'\n@tracer.capture_lambda_handler\ndef provision_tenant(event, context):\n    tenant_details = json.loads(event['body'])\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'stackName': stack_name.format(tenant_details['tenantId']),\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_codepipeline = codepipeline.start_pipeline_execution(\n            name='serverless-saas-pipeline'\n        )\n\n        logger.info(response_ddb)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Provisioning Started\")\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef deprovision_tenant(event, context):\n    logger.info(\"Request received to deprovision a tenant\")\n    \n    tenantid_to_deprovision = event['tenantId']\n    \n    try:          \n        response_ddb = table_tenant_stack_mapping.delete_item(\n            Key={\n                    'tenantId': tenantid_to_deprovision                    \n                }\n            )    \n        \n        logger.info(response_ddb)\n\n        response_cloudformation = cloudformation.delete_stack(\n            StackName=stack_name.format(tenantid_to_deprovision)\n        )\n\n        logger.info(response_cloudformation)\n\n    except Exception as e:\n        raise\n    else:\n        return utils.create_success_response(\"Tenant Deprovisioning Started\")\n\n \n"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/tenant-registration.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport utils\nimport uuid\nimport logger\nimport requests\nimport re\n\nregion = os.environ['AWS_REGION']\ncreate_tenant_admin_user_resource_path = os.environ['CREATE_TENANT_ADMIN_USER_RESOURCE_PATH']\ncreate_tenant_resource_path = os.environ['CREATE_TENANT_RESOURCE_PATH']\nprovision_tenant_resource_path = os.environ['PROVISION_TENANT_RESOURCE_PATH']\n\nplatinum_tier_api_key = os.environ['PLATINUM_TIER_API_KEY']\npremium_tier_api_key = os.environ['PREMIUM_TIER_API_KEY']\nstandard_tier_api_key = os.environ['STANDARD_TIER_API_KEY']\nbasic_tier_api_key = os.environ['BASIC_TIER_API_KEY']\n\nlambda_client = boto3.client('lambda')\n\n\ndef register_tenant(event, context):\n    try:\n        api_key=''\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n        tenant_details['dedicatedTenancy'] = 'false'\n\n        if (tenant_details['tenantTier'].upper() == utils.TenantTier.PLATINUM.value.upper()):\n            tenant_details['dedicatedTenancy'] = 'true'\n            api_key = platinum_tier_api_key\n        elif (tenant_details['tenantTier'].upper() == utils.TenantTier.PREMIUM.value.upper()):\n            api_key = premium_tier_api_key\n        elif (tenant_details['tenantTier'].upper() == utils.TenantTier.STANDARD.value.upper()):\n            api_key = standard_tier_api_key\n        elif (tenant_details['tenantTier'].upper() == utils.TenantTier.BASIC.value.upper()):\n            api_key = basic_tier_api_key\n        \n        tenant_details['tenantId'] = tenant_id\n        tenant_details['apiKey'] = api_key\n        \n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n        \n        logger.info (create_user_response)\n        tenant_details['userPoolId'] = create_user_response['message']['userPoolId']\n        tenant_details['appClientId'] = create_user_response['message']['appClientId']\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n        if (tenant_details['dedicatedTenancy'].upper() == 'TRUE'):\n            provision_tenant_response = __provision_tenant(tenant_details, headers, auth, host, stage_name)\n            logger.info(provision_tenant_response)\n\n        \n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\ndef __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_admin_user_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while calling the create tenant admin user service')\n        raise Exception('Error occured while calling the create tenant admin user service', e)\n    else:\n        return response_json\n\ndef __create_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, create_tenant_resource_path])\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()\n    except Exception as e:\n        logger.error('Error occured while creating the tenant record in table')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\ndef __provision_tenant(tenant_details, headers, auth, host, stage_name):\n    try:\n        url = ''.join(['https://', host, '/', stage_name, provision_tenant_resource_path])\n        logger.info(url)\n        response = requests.post(url, data=json.dumps(tenant_details), auth=auth, headers=headers) \n        response_json = response.json()['message']\n    except Exception as e:\n        logger.error('Error occured while provisioning the tenant')\n        raise Exception('Error occured while creating the tenant record in table', e) \n    else:\n        return response_json\n\n              \n"
  },
  {
    "path": "Solution/Lab6/server/TenantManagementService/user-management.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport os\nimport sys\nimport logger \nimport utils\nimport metrics_manager\nimport auth_manager\nfrom boto3.dynamodb.conditions import Key\nfrom aws_lambda_powertools import Tracer\ntracer = Tracer()\n\nclient = boto3.client('cognito-idp')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_user_map = dynamodb.Table('ServerlessSaaS-TenantUserMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\n\ndef create_tenant_admin_user(event, context):\n    tenant_user_pool_id = os.environ['TENANT_USER_POOL_ID']\n    tenant_app_client_id = os.environ['TENANT_APP_CLIENT_ID']\n    \n    tenant_details = json.loads(event['body'])\n    tenant_id = tenant_details['tenantId']\n    logger.info(tenant_details)\n\n    user_mgmt = UserManagement()\n\n    if (tenant_details['dedicatedTenancy'] == 'true'):\n        user_pool_response = user_mgmt.create_user_pool(tenant_id)\n        user_pool_id = user_pool_response['UserPool']['Id']\n        logger.info (user_pool_id)\n        \n        app_client_response = user_mgmt.create_user_pool_client(user_pool_id)\n        logger.info(app_client_response)\n        app_client_id = app_client_response['UserPoolClient']['ClientId']\n        user_pool_domain_response = user_mgmt.create_user_pool_domain(user_pool_id, tenant_id)\n        \n        logger.info (\"New Tenant Created\")\n    else:\n        user_pool_id = tenant_user_pool_id\n        app_client_id = tenant_app_client_id\n\n    #Add tenant admin now based upon user pool\n    tenant_user_group_response = user_mgmt.create_user_group(user_pool_id,tenant_id,\"User group for tenant {0}\".format(tenant_id))\n\n    tenant_admin_user_name = 'tenant-admin-{0}'.format(tenant_details['tenantId'])\n\n    create_tenant_admin_response = user_mgmt.create_tenant_admin(user_pool_id, tenant_admin_user_name, tenant_details)\n    \n    add_tenant_admin_to_group_response = user_mgmt.add_user_to_group(user_pool_id, tenant_admin_user_name, tenant_user_group_response['Group']['GroupName'])\n    \n    tenant_user_mapping_response = user_mgmt.create_user_tenant_mapping(tenant_admin_user_name,tenant_id)\n    \n    response = {\"userPoolId\": user_pool_id, \"appClientId\": app_client_id, \"tenantAdminUserName\": tenant_admin_user_name}\n    return utils.create_success_response(response)\n\n@tracer.capture_lambda_handler\n#only tenant admin can create users\ndef create_user(event, context):\n    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n\n    user_details = json.loads(event['body'])\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to create new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']    \n    else:\n        user_tenant_id = tenant_id\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        metrics_manager.record_metric(event, \"UserCreated\", \"Count\", 1)\n        response = client.admin_create_user(\n            Username=user_details['userName'],\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['userEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': user_details['userRole'] \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_tenant_id\n                }\n            ]\n        )\n        \n        logger.log_with_tenant_context(event, response)\n        user_mgmt = UserManagement()\n        user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], user_tenant_id)\n        response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], user_tenant_id)\n\n        logger.log_with_tenant_context(event, \"Request completed to create new user\")\n        return utils.create_success_response(\"New user created\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can create user!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\ndef get_users(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']  \n    users = []  \n    \n    \n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get users\")\n\n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        response = client.list_users(\n            UserPoolId=user_pool_id\n        )\n        logger.log_with_tenant_context(event, response) \n        num_of_users = len(response['Users'])\n        metrics_manager.record_metric(event, \"Number of users\", \"Count\", num_of_users)\n        if (num_of_users > 0):\n            for user in response['Users']:\n                is_same_tenant_user = False\n                user_info = UserInfo()\n                for attr in user[\"Attributes\"]:\n                    if(attr[\"Name\"] == \"custom:tenantId\" and attr[\"Value\"] == tenant_id):\n                        is_same_tenant_user = True\n                        user_info.tenant_id = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"custom:userRole\"):\n                        user_info.user_role = attr[\"Value\"]\n\n                    if(attr[\"Name\"] == \"email\"):\n                        user_info.email = attr[\"Value\"] \n                if(is_same_tenant_user):\n                    user_info.enabled = user[\"Enabled\"]\n                    user_info.created = user[\"UserCreateDate\"]\n                    user_info.modified = user[\"UserLastModifiedDate\"]\n                    user_info.status = user[\"UserStatus\"] \n                    user_info.user_name = user[\"Username\"]\n                    users.append(user_info)                    \n        \n        return utils.generate_response(users)\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized.\")        \n        return utils.create_unauthorized_response()\n   \n\n\n\n@tracer.capture_lambda_handler\ndef get_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    user_name = event['pathParameters']['username']  \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to get user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']      \n\n    if (auth_manager.isTenantUser(user_role) and user_name != requesting_user_name):        \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. User can only get its information.\")        \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            logger.log_with_tenant_context(event, \"Request completed to get new user\")\n            return utils.create_success_response(user_info.__dict__)\n\n@tracer.capture_lambda_handler\ndef update_user(event, context):\n    requesting_user_name = event['requestContext']['authorizer']['userName']    \n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']    \n    \n    user_details = json.loads(event['body'])\n\n    user_name = event['pathParameters']['username']    \n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to update user\")\n\n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = user_details['tenantId']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantUser(user_role)):                \n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can update user!\")         \n        return utils.create_unauthorized_response()\n    else:\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserUpdated\", \"Count\", 1)            \n            response = client.admin_update_user_attributes(\n                Username=user_name,\n                UserPoolId=user_pool_id,\n                UserAttributes=[\n                    {\n                        'Name': 'email',\n                        'Value': user_details['userEmail']\n                    },\n                    {\n                        'Name': 'custom:userRole',\n                        'Value': user_details['userRole'] \n                    }\n                ]\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to update user\")\n            return utils.create_success_response(\"user updated\")    \n\n@tracer.capture_lambda_handler\ndef disable_user(event, context):\n    tenant_id = event['requestContext']['authorizer']['tenantId']    \n    user_pool_id = event['requestContext']['authorizer']['userPoolId']    \n    user_role = event['requestContext']['authorizer']['userRole']\n    user_name = event['pathParameters']['username']\n\n    tracer.put_annotation(key=\"TenantId\", value=tenant_id)\n    \n    logger.log_with_tenant_context(event, \"Request received to disable new user\")\n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        user_tenant_id = event['queryStringParameters']['tenantid']\n        tenant_details = table_tenant_details.get_item( \n            Key ={\n                'tenantId': user_tenant_id\n            }\n        )\n        logger.info(tenant_details)\n        user_pool_id = tenant_details['Item']['userPoolId']        \n    \n    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        user_info = get_user_info(event, user_pool_id, user_name)\n        if(not auth_manager.isSystemAdmin(user_role) and user_info.tenant_id!=tenant_id):\n            logger.log_with_tenant_context(event, \"Request completed as unauthorized. Users in other tenants cannot be accessed\")\n            return utils.create_unauthorized_response()\n        else:\n            metrics_manager.record_metric(event, \"UserDisabled\", \"Count\", 1)\n            response = client.admin_disable_user(\n                Username=user_name,\n                UserPoolId=user_pool_id\n            )\n            logger.log_with_tenant_context(event, response)\n            logger.log_with_tenant_context(event, \"Request completed to disable new user\")\n            return utils.create_success_response(\"User disabled\")\n    else:\n        logger.log_with_tenant_context(event, \"Request completed as unauthorized. Only tenant admin or system admin can disable user!\")        \n        return utils.create_unauthorized_response()  \n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef disable_users_by_tenant(event, context):\n    logger.info(\"Request received to disable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if ((auth_manager.isTenantAdmin(user_role) and tenantid_to_update == requesting_tenant_id) or auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_disable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to disable users\")\n        return utils.create_success_response(\"Users disabled\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\n@tracer.capture_lambda_handler\n#this method uses IAM Authorization and protected using a resource policy. This method is also invoked async\ndef enable_users_by_tenant(event, context):\n    logger.info(\"Request received to enable users by tenant\")\n    \n    \n    tenantid_to_update = event['tenantId']\n    tenant_user_pool_id = event['userPoolId']\n    user_role =  event['userRole']\n    requesting_tenant_id = event['requestingTenantId']\n    \n    tracer.put_annotation(key=\"TenantId\", value=tenantid_to_update)\n    \n    \n    if (auth_manager.isSystemAdmin(user_role)):\n        filtering_exp = Key('tenantId').eq(tenantid_to_update)\n        response = table_tenant_user_map.query(KeyConditionExpression=filtering_exp)\n        users = response.get('Items')\n        \n        for user in users:\n            response = client.admin_enable_user(\n                Username=user['userName'],\n                UserPoolId=tenant_user_pool_id\n            )\n            \n        logger.info(response)\n        logger.info(\"Request completed to enable users\")\n        return utils.create_success_response(\"Users enables\")\n    else:\n        logger.info(\"Request completed as unauthorized. Only tenant admin or system admin can update!\")        \n        return utils.create_unauthorized_response()\n\ndef get_user_info(event, user_pool_id, user_name):\n    metrics_manager.record_metric(event, \"UserInfoRequested\", \"Count\", 1)            \n    response = client.admin_get_user(\n            UserPoolId=user_pool_id,\n            Username=user_name\n    )\n    logger.log_with_tenant_context(event, response)\n\n    user_info =  UserInfo()\n    user_info.user_name = response[\"Username\"]\n    for attr in response[\"UserAttributes\"]:\n        if(attr[\"Name\"] == \"custom:tenantId\"):\n            user_info.tenant_id = attr[\"Value\"]\n        if(attr[\"Name\"] == \"custom:userRole\"):\n            user_info.user_role = attr[\"Value\"]    \n        if(attr[\"Name\"] == \"email\"):\n            user_info.email = attr[\"Value\"] \n    logger.log_with_tenant_context(event, user_info)\n    return user_info\n\nclass UserManagement:\n    def create_user_pool(self, tenant_id):\n        application_site_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        email_message = ''.join([\"Login into tenant UI application at \", \n                        application_site_url,\n                        \" with username {username} and temporary password {####}\"])\n        email_subject = \"Your temporary password for tenant UI application\"  \n        response = client.create_user_pool(\n            PoolName= tenant_id + '-ServerlessSaaSUserPool',\n            AutoVerifiedAttributes=['email'],\n            AccountRecoverySetting={\n                'RecoveryMechanisms': [\n                    {\n                        'Priority': 1,\n                        'Name': 'verified_email'\n                    },\n                ]\n            },\n            Schema=[\n                {\n                    'Name': 'email',\n                    'AttributeDataType': 'String',\n                    'Required': True,                    \n                },\n                {\n                    'Name': 'tenantId',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                },            \n                {\n                    'Name': 'userRole',\n                    'AttributeDataType': 'String',\n                    'Required': False,                    \n                }\n            ],\n            AdminCreateUserConfig={\n                'InviteMessageTemplate': {\n                    'EmailMessage': email_message,\n                    'EmailSubject': email_subject\n                }\n            }\n        )    \n        return response\n\n    def create_user_pool_client(self, user_pool_id):\n        user_pool_callback_url = os.environ['TENANT_USER_POOL_CALLBACK_URL']\n        response = client.create_user_pool_client(\n            UserPoolId= user_pool_id,\n            ClientName= 'ServerlessSaaSClient',\n            GenerateSecret= False,\n            AllowedOAuthFlowsUserPoolClient= True,\n            AllowedOAuthFlows=[\n                'code', 'implicit'\n            ],\n            SupportedIdentityProviders=[\n                'COGNITO',\n            ],\n            CallbackURLs=[\n                user_pool_callback_url,\n            ],\n            LogoutURLs= [\n                user_pool_callback_url,\n            ],\n            AllowedOAuthScopes=[\n                'email',\n                'openid',\n                'profile'\n            ],\n            WriteAttributes=[\n                'email',\n                'custom:tenantId'\n            ]\n        )\n        return response\n\n    def create_user_pool_domain(self, user_pool_id, tenant_id):\n        response = client.create_user_pool_domain(\n            Domain= tenant_id + '-serverlesssaas',\n            UserPoolId=user_pool_id\n        )\n        return response\n\n    def create_user_group(self, user_pool_id, group_name, group_description):\n        response = client.create_group(\n            GroupName=group_name,\n            UserPoolId=user_pool_id,\n            Description= group_description,\n            Precedence=0\n        )\n        return response\n\n    def create_tenant_admin(self, user_pool_id, tenant_admin_user_name, user_details):\n        response = client.admin_create_user(\n            Username=tenant_admin_user_name,\n            UserPoolId=user_pool_id,\n            ForceAliasCreation=True,\n            UserAttributes=[\n                {\n                    'Name': 'email',\n                    'Value': user_details['tenantEmail']\n                },\n                {\n                    'Name': 'email_verified',\n                    'Value': 'true'\n                },\n                {\n                    'Name': 'custom:userRole',\n                    'Value': 'TenantAdmin' \n                },            \n                {\n                    'Name': 'custom:tenantId',\n                    'Value': user_details['tenantId']\n                }\n            ]\n        )\n        return response\n\n    def add_user_to_group(self, user_pool_id, user_name, group_name):\n        response = client.admin_add_user_to_group(\n            UserPoolId=user_pool_id,\n            Username=user_name,\n            GroupName=group_name\n        )\n        return response\n\n    def create_user_tenant_mapping(self, user_name, tenant_id):\n        response = table_tenant_user_map.put_item(\n                Item={\n                        'tenantId': tenant_id,\n                        'userName': user_name\n                    }\n                )                    \n\n        return response\n\n\nclass UserInfo:\n    def __init__(self, user_name=None, tenant_id=None, user_role=None, \n    email=None, status=None, enabled=None, created=None, modified=None):\n        self.user_name = user_name\n        self.tenant_id = tenant_id\n        self.user_role = user_role\n        self.email = email\n        self.status = status\n        self.enabled = enabled\n        self.created = created\n        self.modified = modified\n  "
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/.gitignore",
    "content": "*.js\n!jest.config.js\n*.d.ts\nnode_modules\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n\n# Parcel default cache directory\n.parcel-cache\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/.npmignore",
    "content": "*.ts\n!*.d.ts\n\n# CDK asset staging directory\n.cdk.staging\ncdk.out\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/README.md",
    "content": "# Welcome to your CDK TypeScript project!\n\nThis is a blank project for TypeScript development with CDK.\n\nThe `cdk.json` file tells the CDK Toolkit how to execute your app.\n\n## Useful commands\n\n * `npm run build`   compile typescript to js\n * `npm run watch`   watch for changes and compile\n * `npm run test`    perform the jest unit tests\n * `cdk deploy`      deploy this stack to your default AWS account/region\n * `cdk diff`        compare deployed stack with current state\n * `cdk synth`       emits the synthesized CloudFormation template\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/bin/pipeline.ts",
    "content": "#!/usr/bin/env node\nimport 'source-map-support/register';\nimport * as cdk from 'aws-cdk-lib';\nimport { ServerlessSaaSStack } from '../lib/serverless-saas-stack';\n\nconst app = new cdk.App();\nnew ServerlessSaaSStack(app, 'serverless-saas-pipeline');\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/cdk.json",
    "content": "{\n  \"app\": \"npx ts-node bin/pipeline.ts\",\n  \"context\": {}\n}\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/jest.config.js",
    "content": "module.exports = {\n  roots: ['<rootDir>/test'],\n  testMatch: ['**/*.test.ts'],\n  transform: {\n    '^.+\\\\.tsx?$': 'ts-jest'\n  }\n};\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/lib/serverless-saas-stack.ts",
    "content": "// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n// SPDX-License-Identifier: MIT-0\n\nimport { Construct } from 'constructs';\nimport * as cdk from 'aws-cdk-lib';\n\nimport * as s3 from 'aws-cdk-lib/aws-s3';\nimport * as codecommit from 'aws-cdk-lib/aws-codecommit';\nimport * as codepipeline from 'aws-cdk-lib/aws-codepipeline';\nimport * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';\nimport * as codebuild from 'aws-cdk-lib/aws-codebuild';\n\nimport { Function, Runtime, AssetCode } from 'aws-cdk-lib/aws-lambda';\nimport { PolicyStatement } from 'aws-cdk-lib/aws-iam';\nimport { Duration } from 'aws-cdk-lib';\n\n\nexport class ServerlessSaaSStack extends cdk.Stack {\n  constructor(scope: Construct, id: string, props?: cdk.StackProps) {\n    super(scope, id, props);\n\n    const artifactsBucket = new s3.Bucket(this, \"ArtifactsBucket\", {\n      encryption: s3.BucketEncryption.S3_MANAGED,\n    });\n\n    //Since this lambda is invoking cloudformation which is inturn deploying AWS resources, we are giving overly permissive permissions to this lambda. \n    //You can limit this based upon your use case and AWS Resources you need to deploy.\n    const lambdaPolicy = new PolicyStatement()\n        lambdaPolicy.addActions(\"*\")\n        lambdaPolicy.addResources(\"*\")\n\n    const lambdaFunction = new Function(this, \"deploy-tenant-stack\", {\n        handler: \"lambda-deploy-tenant-stack.lambda_handler\",\n        runtime: Runtime.PYTHON_3_9,\n        code: new AssetCode(`./resources`),\n        memorySize: 512,\n        timeout: Duration.seconds(10),\n        environment: {\n            BUCKET: artifactsBucket.bucketName,\n        },\n        initialPolicy: [lambdaPolicy],\n    })\n\n    // Pipeline creation starts\n    const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {\n      pipelineName: 'serverless-saas-pipeline',\n      artifactBucket: artifactsBucket\n    });\n\n    // Import existing CodeCommit sam-app repository\n    const codeRepo = codecommit.Repository.fromRepositoryName(\n      this,\n      'AppRepository', \n      'aws-serverless-saas-workshop'\n    );\n\n    // Declare source code as an artifact\n    const sourceOutput = new codepipeline.Artifact();\n\n    // Add source stage to pipeline\n    pipeline.addStage({\n      stageName: 'Source',\n      actions: [\n        new codepipeline_actions.CodeCommitSourceAction({\n          actionName: 'CodeCommit_Source',\n          repository: codeRepo,\n          branch: 'main',\n          output: sourceOutput,\n          variablesNamespace: 'SourceVariables'\n        }),\n      ],\n    });\n\n    // Declare build output as artifacts\n    const buildOutput = new codepipeline.Artifact();\n\n\n\n    //Declare a new CodeBuild project\n    const buildProject = new codebuild.PipelineProject(this, 'Build', {\n      buildSpec : codebuild.BuildSpec.fromSourceFilename(\"Lab6/server/tenant-buildspec.yml\"),\n      environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4 },\n      environmentVariables: {\n        'PACKAGE_BUCKET': {\n          value: artifactsBucket.bucketName\n        }\n      }\n    });\n\n    \n\n    // Add the build stage to our pipeline\n    pipeline.addStage({\n      stageName: 'Build',\n      actions: [\n        new codepipeline_actions.CodeBuildAction({\n          actionName: 'Build-Serverless-SaaS',\n          project: buildProject,\n          input: sourceOutput,\n          outputs: [buildOutput],\n        }),\n      ],\n    });\n\n    const deployOutput = new codepipeline.Artifact();\n\n\n    //Add the Lambda function that will deploy the tenant stack in a multitenant way\n    pipeline.addStage({\n      stageName: 'Deploy',\n      actions: [\n        new codepipeline_actions.LambdaInvokeAction({\n          actionName: 'DeployTenantStack',\n          lambda: lambdaFunction,\n          inputs: [buildOutput],\n          outputs: [deployOutput],\n          userParameters: {\n            'artifact': 'Artifact_Build_Build-Serverless-SaaS',\n            'template_file': 'packaged.yaml',\n            'commit_id': '#{SourceVariables.CommitId}'\n          }\n        }),\n      ],\n    });    \n  }\n}\n"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/package.json",
    "content": "\n{\n  \"name\": \"pipeline\",\n  \"version\": \"0.1.0\",\n  \"bin\": {\n    \"pipeline\": \"bin/pipeline.js\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"watch\": \"tsc -w\",\n    \"test\": \"jest\",\n    \"cdk\": \"cdk\"\n  },\n  \"devDependencies\": {\n    \"@aws-cdk/assert\": \"1.64.1\",\n    \"@types/jest\": \"^26.0.10\",\n    \"@types/node\": \"10.17.27\",\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"jest\": \"^26.4.2\",\n    \"node-notifier\": \"^8.0.1\",\n    \"ts-jest\": \"^26.2.0\",\n    \"ts-node\": \"^8.1.0\",\n    \"typescript\": \"4.9.5\",\n    \"@types/prettier\": \"2.6.0\"\n  },\n  \"dependencies\": {\n    \"aws-cdk-lib\": \"^2.0.0\",\n    \"constructs\": \"^10.0.0\",\n    \"source-map-support\": \"^0.5.19\"\n  }\n}"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/resources/lambda-deploy-tenant-stack.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom boto3.session import Session\n\nimport json\nimport boto3\nimport zipfile\nimport tempfile\nimport botocore\nimport traceback\nimport time\n\n\n\nprint('Loading function')\n\ncf = boto3.client('cloudformation')\ncode_pipeline = boto3.client('codepipeline')\ndynamodb = boto3.resource('dynamodb')\ntable_tenant_stack_mapping = dynamodb.Table('ServerlessSaaS-TenantStackMapping')\ntable_tenant_details = dynamodb.Table('ServerlessSaaS-TenantDetails')\ntable_tenant_settings = dynamodb.Table('ServerlessSaaS-Settings')\n\n\ndef find_artifact(artifacts, name):\n    \"\"\"Finds the artifact 'name' among the 'artifacts'\n    \n    Args:\n        artifacts: The list of artifacts available to the function\n        name: The artifact we wish to use\n    Returns:\n        The artifact dictionary found\n    Raises:\n        Exception: If no matching artifact is found\n    \n    \"\"\"\n    for artifact in artifacts:\n        if artifact['name'] == name:\n            return artifact\n            \n    raise Exception('Input artifact named \"{0}\" not found in event'.format(name))\n\ndef get_template_url(s3, artifact, file_in_zip):\n    \"\"\"Gets the template artifact\n    \n    Downloads the artifact from the S3 artifact store to a temporary file\n    then extracts the zip and returns the file containing the CloudFormation\n    template.\n    \n    Args:\n        artifact: The artifact to download\n        file_in_zip: The path to the file within the zip containing the template\n        \n    Returns:\n        The CloudFormation template as a string\n        \n    Raises:\n        Exception: Any exception thrown while downloading the artifact or unzipping it\n    \n    \"\"\"\n    tmp_file = tempfile.NamedTemporaryFile()\n    bucket = artifact['location']['s3Location']['bucketName']\n    print(bucket)\n\n    key = artifact['location']['s3Location']['objectKey']\n    print(key)    \n    with tempfile.NamedTemporaryFile() as tmp_file:\n        s3.download_file(bucket, key, tmp_file.name)\n        with zipfile.ZipFile(tmp_file.name, 'r') as zip:\n            extracted_file = zip.extract(file_in_zip, '/tmp/')\n            s3.upload_file(extracted_file, bucket, file_in_zip)\n            template_url =''.join(['https://', bucket,'.s3.amazonaws.com/',file_in_zip])\n            return template_url  \n\n            \n   \ndef update_stack(stack, template_url, params):\n    \"\"\"Start a CloudFormation stack update\n    \n    Args:\n        stack: The stack to update\n        template_url: The template to apply\n        \n    Returns:\n        True if an update was started, false if there were no changes\n        to the template since the last update.\n        \n    Raises:\n        Exception: Any exception besides \"No updates are to be performed.\"\n    \n    \"\"\"\n    try:\n        cf.update_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n        return True\n        \n    except botocore.exceptions.ClientError as e:\n        if e.response['Error']['Message'] == 'No updates are to be performed.':\n            return False\n        else:\n            raise Exception('Error updating CloudFormation stack \"{0}\"'.format(stack), e)\n\ndef stack_exists(stack):\n    \"\"\"Check if a stack exists or not\n    \n    Args:\n        stack: The stack to check\n        \n    Returns:\n        True or False depending on whether the stack exists\n        \n    Raises:\n        Any exceptions raised .describe_stacks() besides that\n        the stack doesn't exist.\n        \n    \"\"\"\n    try:\n        cf.describe_stacks(StackName=stack)\n        return True\n    except botocore.exceptions.ClientError as e:\n        if \"does not exist\" in e.response['Error']['Message']:\n            return False\n        else:\n            raise e\n\ndef create_stack(stack, template_url, params):\n    \"\"\"Starts a new CloudFormation stack creation\n    \n    Args:\n        stack: The stack to be created\n        template_url: The template for the stack to be created with\n        \n    Throws:\n        Exception: Any exception thrown by .create_stack()\n    \"\"\"\n    cf.create_stack(StackName=stack, TemplateURL=template_url, Capabilities=['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], Parameters=params)\n \ndef get_stack_status(stack):\n    \"\"\"Get the status of an existing CloudFormation stack\n    \n    Args:\n        stack: The name of the stack to check\n        \n    Returns:\n        The CloudFormation status string of the stack such as CREATE_COMPLETE\n        \n    Raises:\n        Exception: Any exception thrown by .describe_stacks()\n        \n    \"\"\"\n    stack_description = cf.describe_stacks(StackName=stack)\n    return stack_description['Stacks'][0]['StackStatus']\n  \ndef put_job_success(job, message):\n    \"\"\"Notify CodePipeline of a successful job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    print('Putting job success')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job)\n  \ndef put_job_failure(job, message):\n    \"\"\"Notify CodePipeline of a failed job\n    \n    Args:\n        job: The CodePipeline job ID\n        message: A message to be logged relating to the job status\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_failure_result()\n    \n    \"\"\"\n    print('Putting job failure')\n    print(message)\n    code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'})\n \ndef continue_job_later(job, message):\n    \"\"\"Notify CodePipeline of a continuing job\n    \n    This will cause CodePipeline to invoke the function again with the\n    supplied continuation token.\n    \n    Args:\n        job: The JobID\n        message: A message to be logged relating to the job status\n        continuation_token: The continuation token\n        \n    Raises:\n        Exception: Any exception thrown by .put_job_success_result()\n    \n    \"\"\"\n    \n    # Use the continuation token to keep track of any job execution state\n    # This data will be available when a new job is scheduled to continue the current execution\n    continuation_token = json.dumps({'previous_job_id': job})\n    \n    print('Putting job continuation')\n    print(message)\n    code_pipeline.put_job_success_result(jobId=job, continuationToken=continuation_token)\n\ndef start_update_or_create(job_id, stack, template_url, params):\n    \"\"\"Starts the stack update or create process\n    \n    If the stack exists then update, otherwise create.\n    \n    Args:\n        job_id: The ID of the CodePipeline job\n        stack: The stack to create or update\n        template_url: The template to create/update the stack with\n    \n    \"\"\"\n    if stack_exists(stack):\n        status = get_stack_status(stack)\n        if status not in ['CREATE_COMPLETE', 'ROLLBACK_COMPLETE', 'UPDATE_COMPLETE']:\n            # If the CloudFormation stack is not in a state where\n            # it can be updated again then fail the job right away.\n            put_job_failure(job_id, 'Stack cannot be updated when status is: ' + status)\n            return\n        \n        were_updates = update_stack(stack, template_url, params)\n        \n        if were_updates:\n            # If there were updates then continue the job so it can monitor\n            # the progress of the update.\n            continue_job_later(job_id, 'Stack update started')  \n            \n        else:\n            # If there were no updates then succeed the job immediately \n            put_job_success(job_id, 'There were no stack updates')    \n    else:\n        # If the stack doesn't already exist then create it instead\n        # of updating it.\n        create_stack(stack, template_url, params)\n        # Continue the job so the pipeline will wait for the CloudFormation\n        # stack to be created.\n        continue_job_later(job_id, 'Stack create started') \n\ndef check_stack_update_status(job_id, stack):\n    \"\"\"Monitor an already-running CloudFormation update/create\n    \n    Succeeds, fails or continues the job depending on the stack status.\n    \n    Args:\n        job_id: The CodePipeline job ID\n        stack: The stack to monitor\n    \n    \"\"\"\n    status = get_stack_status(stack)\n    if status in ['UPDATE_COMPLETE', 'CREATE_COMPLETE']:\n        # If the update/create finished successfully then\n        # succeed the job and don't continue.\n        put_job_success(job_id, 'Stack update complete')\n        \n    elif status in ['UPDATE_IN_PROGRESS', 'UPDATE_ROLLBACK_IN_PROGRESS', \n    'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', 'CREATE_IN_PROGRESS', \n    'ROLLBACK_IN_PROGRESS', 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS']:\n        # If the job isn't finished yet then continue it\n        continue_job_later(job_id, 'Stack update still in progress') \n       \n    else:\n        # If the Stack is a state which isn't \"in progress\" or \"complete\"\n        # then the stack update/create has failed so end the job with\n        # a failed result.\n        put_job_failure(job_id, 'Update failed: ' + status)\n\ndef get_user_params(job_data):\n    \"\"\"Decodes the JSON user parameters and validates the required properties.\n    \n    Args:\n        job_data: The job data structure containing the UserParameters string which should be a valid JSON structure\n        \n    Returns:\n        The JSON parameters decoded as a dictionary.\n        \n    Raises:\n        Exception: The JSON can't be decoded or a property is missing.\n        \n    \"\"\"\n    try:\n        # Get the user parameters which contain the stack, artifact and file settings\n        user_parameters = job_data['actionConfiguration']['configuration']['UserParameters']\n        decoded_parameters = json.loads(user_parameters)\n            \n    except Exception:\n        # We're expecting the user parameters to be encoded as JSON\n        # so we can pass multiple values. If the JSON can't be decoded\n        # then fail the job with a helpful message.\n        raise Exception('UserParameters could not be decoded as JSON')\n    \n\n    if 'artifact' not in decoded_parameters:\n        # Validate that the artifact name is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the artifact name')\n    \n    if 'template_file' not in decoded_parameters:\n        # Validate that the template file is provided, otherwise fail the job\n        # with a helpful message.\n        raise Exception('Your UserParameters JSON must include the template file name')\n    \n    return decoded_parameters\n    \ndef setup_s3_client(job_data):\n    \"\"\"Creates an S3 client\n    \n    Uses the credentials passed in the event by CodePipeline. These\n    credentials can be used to access the artifact bucket.\n    \n    Args:\n        job_data: The job data structure\n        \n    Returns:\n        An S3 client with the appropriate credentials\n        \n    \"\"\"\n    # Could not use the artifact credentials to put object to artifacts s3 bucket.\n    # We are running into issue as described in https://github.com/aws/aws-cdk/issues/3274\n     \n    # key_id = job_data['artifactCredentials']['accessKeyId']\n    # key_secret = job_data['artifactCredentials']['secretAccessKey']\n    # session_token = job_data['artifactCredentials']['sessionToken']\n    \n    # session = Session(aws_access_key_id=key_id,\n    #     aws_secret_access_key=key_secret,\n    #     aws_session_token=session_token)\n    # return session.client('s3')\n    return boto3.client('s3')\n\ndef get_tenant_params(tenantId):\n    \"\"\"Get tenant details to be supplied to Cloud formation\n\n    Args:\n        tenantId (str): tenantId for which details are needed\n\n    Returns:\n        params from tenant management table\n    \"\"\"\n    params = []\n    param_tenantid = {}\n    param_tenantid['ParameterKey'] = 'TenantIdParameter'\n    param_tenantid['ParameterValue'] = tenantId\n    params.append(param_tenantid)\n\n    return params\n\ndef add_parameter(params, parameter_key, parameter_value):\n    parameter = {}\n    parameter['ParameterKey'] = parameter_key\n    parameter['ParameterValue'] = parameter_value\n    params.append(parameter)\n\n\n\n\ndef update_tenantstackmapping(tenantId, commit_id):\n    \"\"\"Update the tenant stack mapping table with the code pipeline job id\n\n    Args:\n        tenantId ([string]): tenant id for which data needs to be updated\n        job_id ([type]): current code pipeline job id\n\n    Returns:\n        [type]: [description]\n    \"\"\"\n    response = table_tenant_stack_mapping.update_item(\n            Key={'tenantId': tenantId},\n            UpdateExpression=\"set codeCommitId=:codeCommitId\",\n            ExpressionAttributeValues={\n            ':codeCommitId': commit_id\n            },\n            ReturnValues=\"NONE\") \n    \n    return response\n\ndef lambda_handler(event, context):\n    \"\"\"The Lambda function handler\n    \n    If a continuing job then checks the CloudFormation stack status\n    and updates the job accordingly.\n    \n    If a new job then kick of an update or creation of the target\n    CloudFormation stack.\n    \n    Args:\n        event: The event passed by Lambda\n        context: The context passed by Lambda\n        \n    \"\"\"\n    try:\n        # Extract the Job ID\n        job_id = event['CodePipeline.job']['id']\n        \n        # Extract the Job Data \n        job_data = event['CodePipeline.job']['data']\n        \n        # Extract the params\n        params = get_user_params(job_data)\n        \n        # Get the list of artifacts passed to the function\n        artifacts = job_data['inputArtifacts']\n        \n        artifact = params['artifact']\n        template_file = params['template_file']\n        commit_id = params['commit_id']\n\n        # Get all the stacks for each tenant to be updated/created from tenant stack mapping table\n        mappings = table_tenant_stack_mapping.scan()\n        print (mappings)\n        #Update/Create stacks for all tenants\n        for mapping in mappings['Items']:\n            stack = mapping['stackName']\n            tenantId = mapping['tenantId']\n            applyLatestRelease = mapping['applyLatestRelease']\n\n            if (applyLatestRelease):\n                # Get the parameters to be passed to the Cloudformation from tenant table\n                params = get_tenant_params(tenantId)\n                \n                if 'continuationToken' in job_data:\n                    # If we're continuing then the create/update has already been triggered\n                    # we just need to check if it has finished.\n                    check_stack_update_status(job_id, stack)\n                else:\n                    # Get the artifact details\n                    artifact_data = find_artifact(artifacts, artifact)\n                    # Get S3 client to access artifact with\n                    s3 = setup_s3_client(job_data)\n                    # Get the JSON template file out of the artifact\n                    template_url = get_template_url(s3, artifact_data, template_file)\n                    \n                    # Kick off a stack update or create\n                    start_update_or_create(job_id, stack, template_url, params)  \n\n                    # If we are applying the release, update tenant stack mapping with the pipe line id\n                    update_tenantstackmapping(tenantId, commit_id)\n    except Exception as e:\n        # If any other exceptions which we didn't expect are raised\n        # then fail the job and log the exception message.\n        print('Function failed due to exception.') \n        print(e)\n        traceback.print_exc()\n        put_job_failure(job_id, 'Function exception: ' + str(e))\n    \n    #put_job_success(job_id, \"Changeset executed successfully\")\n    print('Function complete.')   \n    return \"Complete.\""
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/test/pipeline.test.ts",
    "content": "// import { SynthUtils } from '@aws-cdk/assert';\n// import { Stack, App } from 'aws-cdk-lib';\n// import { Template } from 'aws-cdk-lib/assertions';\n// import * as Pipeline from '../lib/serverless-saas-stack';\n\n// test('synthesized cloudformation template should match original template', () => {\n//     const app = new App();\n//     const stack = new Pipeline.ServerlessSaaSStack(app, 'MyTestStack');\n//     const template = Template.fromStack(stack);\n//     expect(template).toMatchSnapshot();\n// });"
  },
  {
    "path": "Solution/Lab6/server/TenantPipeline/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\",\n    \"module\": \"commonjs\",\n    \"lib\": [\"es2018\"],\n    \"declaration\": true,\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": false,\n    \"inlineSourceMap\": true,\n    \"inlineSources\": true,\n    \"experimentalDecorators\": true,\n    \"strictPropertyInitialization\": false,\n    \"typeRoots\": [\"./node_modules/@types\"]\n  },\n  \"exclude\": [\"cdk.out\"]\n}\n"
  },
  {
    "path": "Solution/Lab6/server/custom_resources/requirements.txt",
    "content": "requests\ncrhelper"
  },
  {
    "path": "Solution/Lab6/server/custom_resources/update_settings_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" Called as part of bootstrap template. \n        Inserts/Updates Settings table based upon the resources deployed inside bootstrap template\n        We use these settings inside tenant template\n\n    Args:\n            event ([type]): [description]\n            _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating settings\")\n\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    cognitoUserPoolId = event['ResourceProperties']['cognitoUserPoolId']\n    cognitoUserPoolClientId = event['ResourceProperties']['cognitoUserPoolClientId']\n\n    table_system_settings = dynamodb.Table(settings_table_name)\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'userPoolId-pooled',\n                    'settingValue' : cognitoUserPoolId\n                }\n            )\n\n    response = table_system_settings.put_item(\n            Item={\n                    'settingName': 'appClientId-pooled',\n                    'settingValue' : cognitoUserPoolClientId\n                }\n            )\n\n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab6/server/custom_resources/update_tenant_apigatewayurl.py",
    "content": "import json\nimport boto3\nimport logger\nfrom boto3.dynamodb.conditions import Key\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    client = boto3.client('dynamodb')\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" The URL for Tenant APIs(Product/Order) can differ by tenant.\n        For Pooled tenants it is shared and for Silo (Platinum tier tenants) it is unique to them.\n        This method keeps the URL for Pooled tenants inside Settings Table, since it is shared across multiple tenants,\n        And for Silo tenants inside the tenant management table along with other tenant settings, for that tenant\n\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Details table\")\n\n    tenant_details_table_name = event['ResourceProperties']['TenantDetailsTableName']\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    tenant_id = event['ResourceProperties']['TenantId']\n    tenant_api_gateway_url = event['ResourceProperties']['TenantApiGatewayUrl']\n\n\n    if(tenant_id.lower() =='pooled'):\n        # Note: Tenant management service will use below setting to update apiGatewayUrl for pooled tenants in TenantDetails table\n        settings_table = dynamodb.Table(settings_table_name)\n        settings_table.put_item(Item={\n                    'settingName': 'apiGatewayUrl-Pooled',\n                    'settingValue' : tenant_api_gateway_url                    \n                })\n        \n    else:\n        tenant_details = dynamodb.Table(tenant_details_table_name)\n        response = tenant_details.update_item(\n            Key={'tenantId': tenant_id},\n            UpdateExpression=\"set apiGatewayUrl=:apiGatewayUrl\",\n            ExpressionAttributeValues={\n            ':apiGatewayUrl': tenant_api_gateway_url\n            },\n            ReturnValues=\"NONE\") \n                   \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab6/server/custom_resources/update_tenantstackmap_table.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\nexcept Exception as e:\n    helper.init_failure(e)\n    \n@helper.create\n@helper.update\ndef do_action(event, _):\n    \"\"\" One time entry for pooled tenants inside tenant stack mapping table.\n        This ensures that when code pipeline for tenant template is kicked off, it always create a default stack for pooled tenants.\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"Updating Tenant Stack Map\")\n\n    tenantstackmap_table_name = event['ResourceProperties']['TenantStackMappingTableName']\n    \n    table_stack_mapping = dynamodb.Table(tenantstackmap_table_name)\n    \n    response = table_stack_mapping.put_item(\n            Item={\n                    'tenantId': 'pooled',\n                    'stackName' : 'stack-pooled',\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )                  \n    \n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab6/server/custom_resources/update_usage_plan.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport boto3\nimport logger\n\nfrom crhelper import CfnResource\nhelper = CfnResource()\n\ntry:\n    dynamodb = boto3.resource('dynamodb')\n    apigateway = boto3.client('apigateway')\nexcept Exception as e:\n    helper.init_failure(e)\n\n@helper.create\ndef do_action(event, _):\n    \"\"\" Usage plans are created as part of bootstrap template.\n        This method associates the usage plans for various tiers with tenant Apis\n\n    Args:\n        event ([type]): [description]\n        _ ([type]): [description]\n    \"\"\"\n    logger.info(\"adding api gateway stage to usage plan\")\n    api_id = event['ResourceProperties']['ApiGatewayId']\n    settings_table_name = event['ResourceProperties']['SettingsTableName']\n    is_pooled_deploy = event['ResourceProperties']['IsPooledDeploy']\n    stage = event['ResourceProperties']['Stage']\n    usage_plan_id_basic = event['ResourceProperties']['UsagePlanBasicTier']\n    usage_plan_id_standard = event['ResourceProperties']['UsagePlanStandardTier']\n    usage_plan_id_premium = event['ResourceProperties']['UsagePlanPremiumTier']\n    usage_plan_id_platinum = event['ResourceProperties']['UsagePlanPlatinumTier']\n\n    table_system_settings = dynamodb.Table(settings_table_name)\n\n    if(is_pooled_deploy == \"true\"):\n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_basic,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n\n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_standard,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n        \n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_premium,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n        \n    else:\n        \n        response_apigateway = apigateway.update_usage_plan (\n                usagePlanId=usage_plan_id_platinum,\n                patchOperations=[\n                    {\n                        'op':'add',\n                        'path':'/apiStages',\n                        'value': api_id + \":\" + stage\n                    }\n                ]\n        )\n        \n@helper.update\n@helper.delete\ndef do_nothing(_, __):\n    pass\n\ndef handler(event, context):   \n    helper(event, context)\n        \n    "
  },
  {
    "path": "Solution/Lab6/server/layers/auth_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport utils\n\n# These are the roles being supported in this reference architecture\nclass UserRoles:\n    SYSTEM_ADMIN    = \"SystemAdmin\"\n    CUSTOMER_SUPPORT  = \"CustomerSupport\"\n    TENANT_ADMIN    = \"TenantAdmin\"    \n    TENANT_USER     = \"TenantUser\"\n    \ndef isTenantAdmin(user_role):\n    if (user_role == UserRoles.TENANT_ADMIN):\n        return True\n    else:\n        return False\n\ndef isSystemAdmin(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN):\n        return True\n    else:\n        return False\n\n\ndef isSaaSProvider(user_role):\n    if (user_role == UserRoles.SYSTEM_ADMIN or user_role == UserRoles.CUSTOMER_SUPPORT):\n        return True\n    else:\n        return False\ndef isTenantUser(user_role):\n    if (user_role == UserRoles.TENANT_USER):\n        return True\n    else:\n        return False\n\ndef getPolicyForUser(user_role, service_identifier, tenant_id, region, aws_account_id):\n    \"\"\" This method is being used by Authorizer to get appropriate policy by user role\n\n    Args:\n        user_role (string): UserRoles enum\n        tenant_id (string): \n        region (string): \n        aws_account_id (string):  \n\n    Returns:\n        string: policy that tenant needs to assume\n    \"\"\"\n    iam_policy = \"\"\n    \n    if (isSystemAdmin(user_role)):\n        iam_policy = __getPolicyForSystemAdmin(region, aws_account_id)\n    elif (isTenantAdmin(user_role)):\n        iam_policy = __getPolicyForTenantAdmin(tenant_id, service_identifier, region, aws_account_id)\n    elif (isTenantUser(user_role)):\n        iam_policy = __getPolicyForTenantUser(tenant_id, region, aws_account_id)\n    \n    return iam_policy\n\ndef __getPolicyForSystemAdmin(region, aws_account_id):\n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                    \"dynamodb:UpdateItem\",\n                    \"dynamodb:GetItem\",\n                    \"dynamodb:PutItem\",\n                    \"dynamodb:DeleteItem\",\n                    \"dynamodb:Query\",          \n                    \"dynamodb:Scan\"\n                  ],\n                  \"Resource\": [\n                       \"arn:aws:dynamodb:{0}:{1}:table/*\".format(region, aws_account_id),\n                  ]\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n    \ndef __getPolicyForTenantAdmin(tenant_id, sevice_identifier, region, aws_account_id):\n    if (sevice_identifier == utils.Service_Identifier.SHARED_SERVICES.value):\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:Query\"                   \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantUserMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantDetails\".format(region, aws_account_id)\n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringEquals\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"               \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-TenantStackMapping\".format(region, aws_account_id),\n                        \"arn:aws:dynamodb:{0}:{1}:table/ServerlessSaaS-Settings\".format(region, aws_account_id)\n                    ]\n                }\n            ]\n        }\n    else:\n        policy = {\t\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"    \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                },\n                {\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"dynamodb:UpdateItem\",\n                        \"dynamodb:GetItem\",\n                        \"dynamodb:PutItem\",\n                        \"dynamodb:DeleteItem\",\n                        \"dynamodb:Query\"       \n                    ],\n                    \"Resource\": [\n                        \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                    ],\n                    \"Condition\": {\n                        \"ForAllValues:StringLike\": {\n                            \"dynamodb:LeadingKeys\": [\n                                \"{0}-*\".format(tenant_id)\n                            ]\n                        }\n                    }\n                }\n            ]\n        }\n    return json.dumps(policy)\n\ndef __getPolicyForTenantUser(tenant_id, region, aws_account_id):\n    \n    policy = {\t\n        \"Version\": \"2012-10-17\",\n          \"Statement\": [\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"      \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Product-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              },\n              {\n                  \"Effect\": \"Allow\",\n                  \"Action\": [\n                      \"dynamodb:UpdateItem\",\n                      \"dynamodb:GetItem\",\n                      \"dynamodb:PutItem\",\n                      \"dynamodb:DeleteItem\",\n                      \"dynamodb:Query\"               \n                  ],\n                  \"Resource\": [\n                      \"arn:aws:dynamodb:{0}:{1}:table/Order-*\".format(region, aws_account_id),                      \n                  ],\n                  \"Condition\": {\n                      \"ForAllValues:StringLike\": {\n                          \"dynamodb:LeadingKeys\": [\n                              \"{0}-*\".format(tenant_id)\n                          ]\n                      }\n                  }\n              }\n          ]\n        }\n    \n    return json.dumps(policy)\n\n"
  },
  {
    "path": "Solution/Lab6/server/layers/logger.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nfrom aws_lambda_powertools import Logger\nlogger = Logger()\n\n\"\"\"Log info messages\n\"\"\"\ndef info(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.info (log_message)\n\n\"\"\"Log error messages\n\"\"\"\ndef error(log_message):\n    #logger.structure_logs(append=True, tenant_id=tenant_id)   \n    logger.error (log_message)\n\n\"\"\"Log with tenant context. Extracts tenant context from the lambda events\n\"\"\"\ndef log_with_tenant_context(event, log_message):\n    logger.structure_logs(append=True, tenant_id= event['requestContext']['authorizer']['tenantId'])\n    logger.info (log_message)"
  },
  {
    "path": "Solution/Lab6/server/layers/metrics_manager.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nfrom aws_lambda_powertools import Metrics\n\nmetrics = Metrics()\n\n\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    \"\"\" Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \"\"\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))  \n\n"
  },
  {
    "path": "Solution/Lab6/server/layers/requirements.txt",
    "content": "aws-lambda-powertools[Tracer,Logger,Metrics]\nsimplejson\njsonpickle\naws_requests_auth\npython-jose[cryptography]\naws_requests_auth"
  },
  {
    "path": "Solution/Lab6/server/layers/utils.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport json\nimport jsonpickle\nimport simplejson\n\nimport boto3\nfrom aws_requests_auth.aws_auth import AWSRequestsAuth\nfrom enum import Enum\n\nclass TenantTier(Enum):\n    PLATINUM    = \"Platinum\"\n    PREMIUM     = \"Premium\"\n    STANDARD    = \"Standard\"\n    BASIC       = \"Basic\"\n\n\nclass StatusCodes(Enum):\n    SUCCESS    = 200\n    UN_AUTHORIZED  = 401\n    NOT_FOUND = 404\n    \nclass Service_Identifier(Enum):\n    SHARED_SERVICES     = \"SharedServices\"\n    BUSINESS_SERVICES    = \"BusinessServices\"\n\ndef create_success_response(message):\n    return {\n        \"statusCode\": StatusCodes.SUCCESS.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef create_unauthorized_response():\n    return {\n        \"statusCode\": StatusCodes.UN_AUTHORIZED.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": \"User not authorized to perform this action\"\n        }),\n    }\n\ndef create_notfound_response(message):\n    return {\n        \"statusCode\": StatusCodes.NOT_FOUND.value,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": json.dumps({\n            \"message\": message\n        }),\n    }\n\ndef get_auth(host, region):\n    session = boto3.Session()\n    credentials = session.get_credentials()\n    auth = AWSRequestsAuth(aws_access_key=credentials.access_key,\n                       aws_secret_access_key=credentials.secret_key,\n                       aws_token=credentials.token,\n                       aws_host=host,\n                       aws_region=region,\n                       aws_service='execute-api')\n    return auth                   \n\ndef get_headers(event):\n    return event['headers']\n\n\ndef generate_response(inputObject):\n    return {\n        \"statusCode\": 200,\n        \"headers\": {\n            \"Access-Control-Allow-Headers\" : \"Content-Type, Origin, X-Requested-With, Accept, Authorization, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Origin\",\n            \"Access-Control-Allow-Origin\": \"*\",\n            \"Access-Control-Allow-Methods\": \"OPTIONS,POST,GET,PUT\"\n        },\n        \"body\": encode_to_json_object(inputObject),\n    }\n\ndef  encode_to_json_object(inputObject):\n    jsonpickle.set_encoder_options('simplejson', use_decimal=True, sort_keys=True)\n    jsonpickle.set_preferred_backend('simplejson')\n    return jsonpickle.encode(inputObject, unpicklable=False, use_decimal=True)\n\n\n\n\n\n"
  },
  {
    "path": "Solution/Lab6/server/nested_templates/apigateway.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway, apis, api keys and usage plan as part of bootstrap\nParameters:\n  StageName:\n    Type: String\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String    \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  ApiKeyOperationUsersParameter:\n    Type: String    \n  ApiKeyPlatinumTierParameter:\n    Type: String\n  ApiKeyPremiumTierParameter:\n    Type: String\n  ApiKeyStandardTierParameter:\n    Type: String\n  ApiKeyBasicTierParameter:\n    Type: String  \n  \nResources:\n  ApiGatewayCloudWatchLogRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub apigateway-cloudwatch-publish-role-${AWS::Region}\n      Path: \"/\"\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n  ApiGatewayAttachCloudwatchLogArn:\n    Type: AWS::ApiGateway::Account\n    Properties:\n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchLogRole.Arn\n\n  AdminApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Sub /aws/api-gateway/access-logs-serverless-saas-admin-api\n      RetentionInDays: 30\n  AdminApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: \"/*\"\n          HttpMethod: \"*\"\n      Auth:\n        ResourcePolicy:\n          CustomStatements:\n            - Effect: Allow\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: [\"execute-api:/*/*/*\"]\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/tenant\"\n                   ]\n                  ] \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/POST/user/tenant-admin\"\n                   ]\n                 ]\n                - !Join [\n                   \"\",\n                   [\n                     \"execute-api:/\", !Ref StageName, \"/POST/provisioning\"\n                   ]\n                  ] \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref RegisterTenantLambdaExecutionRoleArn \n                    - !Ref TenantManagementLambdaExecutionRoleArn \n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/disable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/users/enable\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n            - Effect: Deny\n              Principal: \"*\"\n              Action: \"execute-api:Invoke\"\n              Resource: \n                - !Join [ \"\", [\n                     \"execute-api:/\", !Ref StageName, \"/PUT/provisioning/{tenantid}\"\n                   ]\n                 ]                \n              Condition:\n                StringNotEquals:\n                  aws:PrincipalArn:\n                    - !Ref TenantManagementLambdaExecutionRoleArn\n      AccessLogSetting:\n        DestinationArn: !GetAtt AdminApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join [\"\", [\"serverless-saas-admin-api-\", !Ref \"AWS::Region\"]]\n        basePath: !Join [\"\", [\"/\", !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /registration:\n            post:\n              summary: Register a new tenant\n              description: Register a new tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref RegisterTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                   \n          /provisioning:\n            post:\n              summary: provisions resource for new tenant\n              description: provisions resource for new tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref ProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /provisioning/{tenantid}:\n            put:\n              summary: deprovision by tenant\n              description: deprovision by tenant\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:                \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeProvisionTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /tenant/activation/{tenantid}:\n            put:\n              security:\n                - api_key: []\n                - Authorizer: []\n              summary: Activate an existing tenant\n              description: Activate an existing tenant\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - \"\"\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !Ref ActivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /tenants:\n            get:\n              summary: Returns all tenants\n              description: Returns all tenants\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantsFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                \"200\":\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin: \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock                 \n          /tenant:\n            post:\n              summary: Creates a tenant\n              description: Creates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy                \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /tenant/init/{tenantname}:\n            get:\n              summary: Returns a tenant config\n              description: Return a tenant config by a tenant name\n              produces:\n                - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantConfigFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock        \n          /tenant/{tenantid}:\n            get:\n              summary: Returns a tenant\n              description: Return a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy        \n            delete:              \n              summary: Disables a tenant\n              description: Disables a tenant by a tenant id\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DeactivateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:  \n              summary: Updates a tenant\n              description: Updates a tenant\n              produces:\n                - application/json\n              responses: {}\n              security:        \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy            \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n            \n          /user/{username}:\n            get:\n              summary: Returns a user\n              description: Return a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:    \n                - api_key: [] \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy    \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:         \n                - api_key: []       \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref UpdateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:          \n              summary: Diables a user\n              description: Disable a user by a user id    \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n          /user/tenant-admin:\n            post:\n              summary: Creates a tenant admin user\n              description: Creates a tenant admin user\n              produces:\n                - application/json\n              responses: {}\n              security:\n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateTenantAdminUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy          \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock       \n          /user:\n            post:\n              summary: Create a user\n              description: Create a user by a user id\n              produces:\n                - application/json\n              responses: {}\n              security:  \n                - api_key: []          \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref CreateUserFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock \n          /users:\n            get:\n              summary: Get all users by tenantId\n              description: Get all users by tenantId\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref GetUsersFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock         \n          /users/disable:\n            put:\n              summary: disable users by tenant id\n              description: disable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref DisableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n          /users/enable:\n            put:\n              summary: enable users by tenant id\n              description: enable users by tenant id\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              security:     \n                - sigv4Reference: []\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match                \n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref EnableUsersByTenantFunctionArn\n                    - /invocations\n                httpMethod: POST\n                type: AWS\n                requestParameters:\n                  integration.request.header.X-Amz-Invocation-Type: '''Event'''\n                        \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock  \n        components:\n          securitySchemes:      \n            api_key:\n              type: \"apiKey\"\n              name: \"x-api-key\"\n              in: \"header\"     \n            sigv4Reference:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"awsSigv4\"  \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !Ref AuthorizerFunctionArn\n                    - /invocations\n                authorizerResultTtlInSeconds: 60\n                type: \"token\"\n      StageName: prod\n  \n #Create API Keys and Usage Plans\n  APIGatewayApiKeySystemAdmin:\n    Type: AWS::ApiGateway::ApiKey\n    Properties:\n      Description: \"This is the api key to be used by system admin\"\n      Enabled: True\n      Name: Serverless-SaaS-SysAdmin-ApiKey\n      Value: !Ref ApiKeyOperationUsersParameter\n  APIGatewayApiKeyPlatinumTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by platinum tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-PlatinumTier-ApiKey\n      Value: !Ref ApiKeyPlatinumTierParameter\n  APIGatewayApiKeyPremiumTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by premium tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-PremiumTier-ApiKey\n      Value: !Ref ApiKeyPremiumTierParameter\n  APIGatewayApiKeyStandardTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by standard tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-StandardTier-ApiKey\n      Value: !Ref ApiKeyStandardTierParameter\n  APIGatewayApiKeyBasicTier:\n    Type: AWS::ApiGateway::ApiKey\n    Properties: \n      Description: 'This is the api key to be used by basic tier tenants'\n      Enabled: True\n      Name: Serverless-SaaS-BasicTier-ApiKey\n      Value: !Ref ApiKeyBasicTierParameter\n\n  UsagePlanPlatinumTier:   \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for platinum tier tenants\n      Quota:\n        Limit: 10000\n        Period: DAY\n      Throttle:\n        BurstLimit: 300\n        RateLimit: 300\n      UsagePlanName: Plan_Platinum_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanPremiumTier:   \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for premium tier tenants\n      Quota:\n        Limit: 5000\n        Period: DAY\n      Throttle:\n        BurstLimit: 200\n        RateLimit: 100\n      UsagePlanName: Plan_Premium_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanStandardTier: \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for standard tier tenants\n      Quota:\n        Limit: 3000\n        Period: DAY\n      Throttle:\n        BurstLimit: 100\n        RateLimit: 75\n      UsagePlanName: Plan_Standard_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanBasicTier:   \n    Type: 'AWS::ApiGateway::UsagePlan'\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for basic tier tenants\n      Quota:\n        Limit: 500\n        Period: DAY\n      Throttle:\n        BurstLimit: 50\n        RateLimit: 50\n      UsagePlanName: Plan_Basic_Tier\n    DependsOn: \n      - AdminApiGatewayApiprodStage\n  UsagePlanSystemAdmin:\n    Type: \"AWS::ApiGateway::UsagePlan\"\n    Properties:\n      ApiStages:\n        - ApiId: !Ref AdminApiGatewayApi\n          Stage: !Ref StageName\n      Description: Usage plan for system admin\n      Quota:\n        Limit: 10000\n        Period: DAY\n      Throttle:\n        BurstLimit: 5000\n        RateLimit: 500\n      UsagePlanName: System_Admin_Usage_Plan\n    DependsOn:\n      - AdminApiGatewayApiprodStage\n\n  AssociateAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties:\n      KeyId: !Ref APIGatewayApiKeySystemAdmin\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanSystemAdmin\n    DependsOn: UsagePlanSystemAdmin\n  AssociatePlatinumAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyPlatinumTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanPlatinumTier\n    DependsOn: UsagePlanPlatinumTier\n  AssociatePremiumAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyPremiumTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanPremiumTier\n    DependsOn: UsagePlanPremiumTier\n  AssociateStandardAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyStandardTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanStandardTier\n    DependsOn: UsagePlanStandardTier\n  AssociateBasicAPIKeyToUsagePlan:\n    Type: AWS::ApiGateway::UsagePlanKey\n    Properties: \n      KeyId: !Ref APIGatewayApiKeyBasicTier\n      KeyType: API_KEY\n      UsagePlanId: !Ref UsagePlanBasicTier\n    DependsOn: UsagePlanBasicTier\nOutputs:\n  UsagePlanBasicTier: \n    Value: !Ref UsagePlanBasicTier\n  UsagePlanStandardTier: \n    Value: !Ref UsagePlanStandardTier\n  UsagePlanPremiumTier: \n    Value: !Ref UsagePlanPremiumTier\n  UsagePlanPlatinumTier: \n    Value: !Ref UsagePlanPlatinumTier\n  AdminApiGatewayApi:\n    Value: !Ref AdminApiGatewayApi"
  },
  {
    "path": "Solution/Lab6/server/nested_templates/apigateway_lambdapermissions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup api gateway and apis as part of bootstrap\nParameters:\n  RegisterTenantLambdaExecutionRoleArn:\n    Type: String\n  TenantManagementLambdaExecutionRoleArn:\n    Type: String\n  RegisterTenantFunctionArn:\n    Type: String\n  ProvisionTenantFunctionArn:\n    Type: String\n  DeProvisionTenantFunctionArn:\n    Type: String\n  ActivateTenantFunctionArn:\n    Type: String\n  GetTenantsFunctionArn:\n    Type: String\n  CreateTenantFunctionArn:\n    Type: String\n  GetTenantFunctionArn:\n    Type: String\n  DeactivateTenantFunctionArn:\n    Type: String\n  UpdateTenantFunctionArn:\n    Type: String\n  GetTenantConfigFunctionArn:\n    Type: String\n  GetUsersFunctionArn:\n    Type: String   \n  GetUserFunctionArn:\n    Type: String\n  UpdateUserFunctionArn:\n    Type: String\n  DisableUserFunctionArn:\n    Type: String\n  CreateTenantAdminUserFunctionArn:\n    Type: String\n  CreateUserFunctionArn:\n    Type: String\n  DisableUsersByTenantFunctionArn:\n    Type: String\n  EnableUsersByTenantFunctionArn:\n    Type: String\n  AuthorizerFunctionArn:\n    Type: String\n  AdminApiGatewayApi:\n    Type: String\nResources:\n  #provide api gateway permissions to call lambda functions\n  RegisterTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref RegisterTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateTenantAdminUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantAdminUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeProvisionTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeProvisionTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  CreateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DisableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DisableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  EnableUsersByTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref EnableUsersByTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetUsersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUsersFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]   \n  GetUserLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetUserFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref AuthorizerFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*\" ]]\n  CreateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref CreateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  UpdateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref UpdateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantsFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  DeactivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref DeactivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  ActivateTenantLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref ActivateTenantFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]\n  GetTenantConfigLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !Ref GetTenantConfigFunctionArn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref AdminApiGatewayApi, \"/*/*/*\" ]]    "
  },
  {
    "path": "Solution/Lab6/server/nested_templates/cognito.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to setup cognito as part of bootstrap\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Description: \"Enter the role name for system admin\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Description: \"Enter Admin Management userpool call back url\"\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"  \nResources:\n  CognitoUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: PooledTenant-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into tenant UI application at \"\n              - \"https://\"\n              - !Ref TenantUserPoolCallbackURLParameter \n              - \"/\"\n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\" \n            - - \"Your temporary password for tenant UI application\"\n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId          \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:\n        - !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]  \n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [pooledtenant-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoUserPool\n\n  CognitoOperationUsersUserPool:\n    Type: \"AWS::Cognito::UserPool\"\n    Properties:\n      UserPoolName: OperationUsers-ServerlessSaaSUserPool\n      AutoVerifiedAttributes:\n        - \"email\"\n      AccountRecoverySetting:\n        RecoveryMechanisms:\n          - Name: verified_email\n            Priority: 1\n      AdminCreateUserConfig:      \n        InviteMessageTemplate:\n          EmailMessage: !Join \n            - \"\" \n            - - \"Login into admin UI application at \" \n              - \"https://\"\n              - !Ref AdminUserPoolCallbackURLParameter \n              - \"/\" \n              - \" with username {username} and temporary password {####}\"\n          EmailSubject: !Join \n            - \"\"\n            - - \"Your temporary password for admin UI application\"  \n      Schema:\n        - AttributeDataType: \"String\"\n          Name: email\n          Required: True\n          Mutable: True\n        - AttributeDataType: \"String\"\n          Name: tenantId        \n        - AttributeDataType: \"String\"\n          Name: userRole\n          Required: False\n          Mutable: True        \n  CognitoOperationUsersUserPoolClient:\n    Type: \"AWS::Cognito::UserPoolClient\"\n    Properties:\n      ClientName: ServerlessSaaSOperationUsersPoolClient\n      GenerateSecret: false\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n      AllowedOAuthFlowsUserPoolClient: True\n      AllowedOAuthFlows:\n        - code\n        - implicit\n      SupportedIdentityProviders:\n        - COGNITO\n      CallbackURLs:\n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      LogoutURLs:  \n        - !Join [\"\",[\"https://\", !Ref AdminUserPoolCallbackURLParameter, \"/\"]]\n      AllowedOAuthScopes:\n        - email\n        - openid\n        - profile\n      WriteAttributes:\n        - \"email\"\n        - \"custom:tenantId\"\n        - \"custom:userRole\"        \n  CognitoOperationUsersUserPoolDomain:\n    Type: AWS::Cognito::UserPoolDomain\n    Properties:\n      Domain: !Join [\"-\", [operationsusers-serverlesssaas,!Ref \"AWS::AccountId\"]]\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUserGroup:\n    Type: AWS::Cognito::UserPoolGroup\n    Properties:\n      GroupName: SystemAdmins\n      Description: Admin user group\n      Precedence: 0\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAdminUser:\n    Type: AWS::Cognito::UserPoolUser\n    Properties:\n      Username: admin\n      DesiredDeliveryMediums:\n        - EMAIL\n      ForceAliasCreation: true\n      UserAttributes:\n        - Name: email\n          Value: !Ref AdminEmailParameter\n        - Name: custom:tenantId\n          Value: system_admins\n        - Name: custom:userRole\n          Value: !Ref SystemAdminRoleNameParameter\n      UserPoolId: !Ref CognitoOperationUsersUserPool\n  CognitoAddUserToGroup:\n      Type: AWS::Cognito::UserPoolUserToGroupAttachment\n      Properties:\n        GroupName: !Ref CognitoAdminUserGroup\n        Username: !Ref CognitoAdminUser\n        UserPoolId: !Ref CognitoOperationUsersUserPool\n        \nOutputs:\n  CognitoUserPoolId:\n    Value: !Ref CognitoUserPool\n  CognitoUserPoolClientId:\n    Value: !Ref CognitoUserPoolClient\n  CognitoOperationUsersUserPoolId:\n    Value: !Ref CognitoOperationUsersUserPool\n  CognitoOperationUsersUserPoolClientId:\n    Value: !Ref CognitoOperationUsersUserPoolClient\n  CognitoOperationUsersUserPoolProviderURL:\n    Value: !GetAtt CognitoOperationUsersUserPool.ProviderURL"
  },
  {
    "path": "Solution/Lab6/server/nested_templates/custom_resources.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableName:\n    Type: String\n  TenantStackMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  UpdateSettingsTableFunctionArn:\n    Type: String\n  UpdateTenantStackMapTableFunctionArn:\n    Type: String\n  CognitoUserPoolId:\n    Type: String\n  CognitoUserPoolClientId:\n    Type: String\nResources:\n  #Custom resources\n  \n  UpdateSettingsTable:\n    Type: Custom::UpdateSettingsTable\n    Properties:\n      ServiceToken: !Ref UpdateSettingsTableFunctionArn\n      SettingsTableName: !Ref ServerlessSaaSSettingsTableName\n      cognitoUserPoolId: !Ref CognitoUserPoolId\n      cognitoUserPoolClientId: !Ref CognitoUserPoolClientId\n  \n  \n  UpdateTenantStackMap:\n    Type: Custom::UpdateTenantStackMap\n    Properties:\n      ServiceToken: !Ref UpdateTenantStackMapTableFunctionArn\n      TenantStackMappingTableName: !Ref TenantStackMappingTableName"
  },
  {
    "path": "Solution/Lab6/server/nested_templates/lambdafunctions.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy lambda functions as part of bootstrap\nParameters:\n  CognitoOperationUsersUserPoolId:\n    Type: String\n  CognitoOperationUsersUserPoolClientId:\n    Type: String\n  CognitoUserPoolId: \n    Type: String\n  CognitoUserPoolClientId: \n    Type: String\n  TenantDetailsTableArn:\n    Type: String\n  ServerlessSaaSSettingsTableArn:\n    Type: String\n  ApiKeyOperationUsersParameter:\n    Type: String\n  ApiKeyPlatinumTierParameter:\n    Type: String\n  ApiKeyPremiumTierParameter:\n    Type: String\n  ApiKeyStandardTierParameter:\n    Type: String\n  ApiKeyBasicTierParameter:\n    Type: String  \n  TenantStackMappingTableArn:\n    Type: String\n  TenantUserMappingTableArn:\n    Type: String\n  TenantStackMappingTableName:\n    Type: String\n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Description: \"Enter Tenant Management userpool call back url\"    \nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG        \n        POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n        \nResources:\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-dependencies\n      Description: Utilities for project\n      ContentUri: ../layers/\n      CompatibleRuntimes:\n        - python3.9\n      LicenseInfo: \"MIT\"\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n\n  #Tenant Authorizer\n  AuthorizerExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: authorizer-execution-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess   \n      Policies:      \n        - PolicyName: authorizer-execution-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:List*                                    \n                Resource:\n                  - !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*    \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn    \n  AuthorizerAccessRole:\n    Type: AWS::IAM::Role\n    DependsOn: AuthorizerExecutionRole\n    Properties:\n      RoleName: authorizer-access-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              AWS:\n                - !GetAtt 'AuthorizerExecutionRole.Arn'\n            Action:\n              - sts:AssumeRole       \n      Policies:\n        - PolicyName: authorizer-access-role-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:BatchGetItem     \n                  - dynamodb:GetItem\n                  - dynamodb:PutItem\n                  - dynamodb:DeleteItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Query\n                  - dynamodb:Scan     \n                Resource:  \n                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*\n  SharedServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: AuthorizerAccessRole\n    Properties:\n      CodeUri: ../Resources/\n      Handler: shared_service_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !GetAtt AuthorizerExecutionRole.Arn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !Ref CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !Ref CognitoOperationUsersUserPoolClientId\n          OPERATION_USERS_API_KEY : !Ref ApiKeyOperationUsersParameter\n        \n          \n  \n  #Create user pool for the tenant\n  TenantUserPoolLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-userpool-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-userpool-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n  CreateUserLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub create-user-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole          \n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-user-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - cognito-idp:*\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource:\n                  - !Ref TenantUserMappingTableArn\n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource:\n                  - !Ref TenantDetailsTableArn\n  CreateTenantAdminUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_tenant_admin_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn      \n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          TENANT_USER_POOL_ID: !Ref CognitoUserPoolId\n          TENANT_APP_CLIENT_ID: !Ref CognitoUserPoolClientId\n          TENANT_USER_POOL_CALLBACK_URL: !Join [\"\",[\"https://\",!Ref TenantUserPoolCallbackURLParameter, \"/\"]]\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateTenantAdmin\"   \n\n  #User management\n  CreateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: CreateUserLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.create_user\n      Runtime: python3.9\n      Role: !GetAtt CreateUserLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.CreateUser\"\n\n  UpdateUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.update_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.UpdateUser\"\n\n  DisableUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUser\"\n         \n  DisableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.disable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.DisableUsersByTenant\"\n           \n  EnableUsersByTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.enable_users_by_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.EnableUsersByTenant\"\n        \n  GetUserFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_user\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUser\"\n         \n  GetUsersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantUserPoolLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: user-management.get_users\n      Runtime: python3.9\n      Role: !GetAtt TenantUserPoolLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers      \n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"UserManagement.GetUsers\"  \n             \n  \n  #Tenant Management\n  TenantManagementLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-management-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub create-tenant-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                  - dynamodb:GetItem\n                  - dynamodb:UpdateItem\n                  - dynamodb:Scan\n                  - dynamodb:Query\n                Resource:\n                  - !Ref TenantDetailsTableArn  \n                  - !Join [\"\", [!Ref TenantDetailsTableArn, '/index/*']] \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem                  \n                Resource:\n                  - !Ref ServerlessSaaSSettingsTableArn                 \n  CreateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.create_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.CreateTenant\"\n          \n  ActivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.activate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.ActivateTenant\"\n          ENABLE_USERS_BY_TENANT: \"/users/enable\"\n          PROVISION_TENANT: \"/provisioning/\"\n          \n  GetTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.GetTenant\"    \n        \n  DeactivateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.deactivate_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.DeactivateTenant\"\n          DEPROVISION_TENANT: \"/provisioning/\"\n          DISABLE_USERS_BY_TENANT: \"/users/disable\"\n                 \n  UpdateTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.update_tenant\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"TenantManagement.UpdateTenant\"\n          PLATINUM_TIER_API_KEY: !Ref ApiKeyPlatinumTierParameter\n          PREMIUM_TIER_API_KEY: !Ref ApiKeyPremiumTierParameter\n          STANDARD_TIER_API_KEY: !Ref ApiKeyStandardTierParameter\n          BASIC_TIER_API_KEY: !Ref ApiKeyBasicTierParameter\n                 \n  GetTenantsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.get_tenants\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers \n      \n  GetTenantConfigFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: TenantManagementLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-management.load_tenant_config\n      Runtime: python3.9\n      Role: !GetAtt TenantManagementLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n                  \n  \n  #Tenant Registration\n  RegisterTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-registration-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess      \n  RegisterTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: RegisterTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-registration.register_tenant\n      Runtime: python3.9\n      Role: !GetAtt RegisterTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: # Need to find a better way than hard coding resource paths\n          CREATE_TENANT_ADMIN_USER_RESOURCE_PATH: \"/user/tenant-admin\"\n          CREATE_TENANT_RESOURCE_PATH: \"/tenant\"\n          PROVISION_TENANT_RESOURCE_PATH: \"/provisioning\"\n          PLATINUM_TIER_API_KEY: !Ref ApiKeyPlatinumTierParameter\n          PREMIUM_TIER_API_KEY: !Ref ApiKeyPremiumTierParameter\n          STANDARD_TIER_API_KEY: !Ref ApiKeyStandardTierParameter\n          BASIC_TIER_API_KEY: !Ref ApiKeyBasicTierParameter\n          POWERTOOLS_SERVICE_NAME: \"TenantRegistration.RegisterTenant\"  \n  \n  #Tenant Provisioning\n  ProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-provisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-provisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem      \n                  - dynamodb:DeleteItem                  \n                Resource:\n                  - !Ref TenantStackMappingTableArn\n              - Effect: Allow\n                Action:\n                  - codepipeline:StartPipelineExecution\n                Resource:\n                  - !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:serverless-saas-pipeline\n              - Effect: Allow\n                Action:\n                  - cloudformation:DeleteStack\n                Resource: \"*\"                        \n  ProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.provision_tenant\n      Runtime: python3.9\n      Role: !GetAtt ProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n        \n  DeProvisionTenantLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub tenant-deprovisioning-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub tenant-deprovisioning-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            #Since this lambda is invoking cloudformation which is inturn removing AWS resources, we are giving overly permissive permissions to this lambda. \n            #You can limit this based upon your use case and AWS Resources you need to remove.\n            Statement: \n              - Effect: Allow\n                Action: \"*\"                  \n                Resource: \"*\"                     \n  DeProvisionTenantFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: DeProvisionTenantLambdaExecutionRole\n    Properties:\n      CodeUri: ../TenantManagementService/\n      Handler: tenant-provisioning.deprovision_tenant\n      Runtime: python3.9\n      Role: !GetAtt DeProvisionTenantLambdaExecutionRole.Arn\n      Tracing: Active\n      Layers:\n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables: \n          TENANT_STACK_MAPPING_TABLE_NAME: !Ref TenantStackMappingTableName\n         \n  UpdateSettingsTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-settingstable-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-settingstable-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref ServerlessSaaSSettingsTableArn\n  UpdateSettingsTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateSettingsTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_settings_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateSettingsTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantStackMapTableLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: !Sub update-tenantstackmap-lambda-execution-role-${AWS::Region}\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Sub update-tenantstackmap-lambda-execution-policy-${AWS::Region}\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Ref TenantStackMappingTableArn\n  UpdateTenantStackMapTableFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantStackMapTableLambdaExecutionRole\n    Properties:\n      CodeUri: ../custom_resources/\n      Handler: update_tenantstackmap_table.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantStackMapTableLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers        \nOutputs:\n  RegisterTenantLambdaExecutionRoleArn: \n    Value: !GetAtt RegisterTenantLambdaExecutionRole.Arn          \n  TenantManagementLambdaExecutionRoleArn: \n    Value: !GetAtt TenantManagementLambdaExecutionRole.Arn          \n  RegisterTenantFunctionArn: \n    Value: !GetAtt RegisterTenantFunction.Arn\n  ProvisionTenantFunctionArn: \n    Value: !GetAtt ProvisionTenantFunction.Arn\n  DeProvisionTenantFunctionArn: \n    Value: !GetAtt DeProvisionTenantFunction.Arn\n  ActivateTenantFunctionArn: \n    Value: !GetAtt ActivateTenantFunction.Arn\n  GetTenantConfigFunctionArn:\n    Value: !GetAtt GetTenantConfigFunction.Arn  \n  GetTenantsFunctionArn: \n    Value: !GetAtt GetTenantsFunction.Arn\n  CreateTenantFunctionArn: \n    Value: !GetAtt CreateTenantFunction.Arn\n  GetTenantFunctionArn: \n    Value: !GetAtt GetTenantFunction.Arn          \n  DeactivateTenantFunctionArn: \n    Value: !GetAtt DeactivateTenantFunction.Arn          \n  UpdateTenantFunctionArn: \n    Value: !GetAtt UpdateTenantFunction.Arn\n  GetUsersFunctionArn:\n    Value: !GetAtt GetUsersFunction.Arn            \n  GetUserFunctionArn: \n    Value: !GetAtt GetUserFunction.Arn          \n  UpdateUserFunctionArn: \n    Value: !GetAtt UpdateUserFunction.Arn          \n  DisableUserFunctionArn: \n    Value: !GetAtt DisableUserFunction.Arn\n  CreateTenantAdminUserFunctionArn: \n    Value: !GetAtt CreateTenantAdminUserFunction.Arn\n  CreateUserFunctionArn: \n    Value: !GetAtt CreateUserFunction.Arn\n  DisableUsersByTenantFunctionArn: \n    Value: !GetAtt DisableUsersByTenantFunction.Arn\n  EnableUsersByTenantFunctionArn: \n    Value: !GetAtt EnableUsersByTenantFunction.Arn          \n  SharedServicesAuthorizerFunctionArn: \n    Value: !GetAtt SharedServicesAuthorizerFunction.Arn      \n  AuthorizerExecutionRoleArn:\n    Value: !GetAtt AuthorizerExecutionRole.Arn      \n  UpdateSettingsTableFunctionArn:\n    Value: !GetAtt UpdateSettingsTableFunction.Arn    \n  UpdateTenantStackMapTableFunctionArn:\n    Value: !GetAtt UpdateTenantStackMapTableFunction.Arn\n  "
  },
  {
    "path": "Solution/Lab6/server/nested_templates/tables.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to create dynamodb tables as part of bootstrap\nResources:\n  ServerlessSaaSSettingsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: settingName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: settingName\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-Settings\n  TenantStackMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantStackMapping\n  TenantDetailsTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: tenantName\n          AttributeType: S   \n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      GlobalSecondaryIndexes:\n        - IndexName: ServerlessSaas-TenantConfig\n          KeySchema:\n            - AttributeName: tenantName\n              KeyType: HASH\n          Projection:\n            NonKeyAttributes: \n              - userPoolId\n              - appClientId\n              - apiGatewayUrl\n            ProjectionType: INCLUDE      \n      TableName: ServerlessSaaS-TenantDetails\n  TenantUserMappingTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: tenantId\n          AttributeType: S\n        - AttributeName: userName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: tenantId\n          KeyType: HASH\n        - AttributeName: userName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST\n      TableName: ServerlessSaaS-TenantUserMapping\n      GlobalSecondaryIndexes: \n        - IndexName: UserName\n          KeySchema: \n            - AttributeName: userName\n              KeyType: HASH\n            - AttributeName: tenantId\n              KeyType: RANGE\n          Projection:\n            ProjectionType: ALL\nOutputs:\n  ServerlessSaaSSettingsTableArn: \n    Value: !GetAtt ServerlessSaaSSettingsTable.Arn\n  ServerlessSaaSSettingsTableName: \n    Value: !Ref ServerlessSaaSSettingsTable\n  TenantStackMappingTableArn: \n    Value: !GetAtt TenantStackMappingTable.Arn\n  TenantStackMappingTableName: \n    Value: !Ref TenantStackMappingTable\n  TenantDetailsTableArn: \n    Value: !GetAtt TenantDetailsTable.Arn\n  TenantDetailsTableName: \n    Value: !Ref TenantDetailsTable\n  TenantUserMappingTableArn: \n    Value: !GetAtt TenantUserMappingTable.Arn\n  TenantUserMappingTableName: \n    Value: !Ref TenantUserMappingTable"
  },
  {
    "path": "Solution/Lab6/server/nested_templates/userinterface.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code\nParameters:\n  IsCloudFrontAndS3PreProvisioned:\n    Type: String\n    Default: false    \n    Description: \"Tells if cloudfront and s3 buckets are pre-provisioned or not. \n    They get pre-provisioned when the workshop is running as a part of AWS Event through AWS event engine tool.\"\nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref IsCloudFrontAndS3PreProvisioned, true] ]  \nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n\n  AppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    Condition: IsNotRunningInEventEngine\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Condition: IsNotRunningInEventEngine\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'       \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Condition: IsNotRunningInEventEngine\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName    \n    Condition: IsNotRunningInEventEngine\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName   \n    Condition: IsNotRunningInEventEngine\n         "
  },
  {
    "path": "Solution/Lab6/server/shared-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\""
  },
  {
    "path": "Solution/Lab6/server/shared-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to Bootstrap the Common Resources\nParameters:\n  AdminEmailParameter:\n    Type: String\n    Default: \"test@test.com\"\n    Description: \"Enter system admin email address\"\n  SystemAdminRoleNameParameter:\n    Type: String\n    Default: \"SystemAdmin\"\n    Description: \"Enter the role name for system admin\"\n  ApiKeyOperationUsersParameter:\n    Type: String\n    Default: \"9a7743fa-3ae7-11eb-adc1-0242ac120002\"\n    Description: \"Enter default api key value to be used by api gateway for system admins\"\n  ApiKeyPlatinumTierParameter:\n    Type: String\n    Default: \"88b43c36-802e-11eb-af35-38f9d35b2c15\"\n    Description: \"Enter default api key value to be used by api gateway for platinum tier tenants\"\n  ApiKeyPremiumTierParameter:\n    Type: String\n    Default: \"6db2bdc2-6d96-11eb-a56f-38f9d33cfd0f\"\n    Description: \"Enter default api key value to be used by api gateway for premium tier tenants\"\n  ApiKeyStandardTierParameter:\n    Type: String\n    Default: \"b1c735d8-6d96-11eb-a28b-38f9d33cfd0f\"\n    Description: \"Enter default api key value to be used by api gateway for standard tier tenants\"\n  ApiKeyBasicTierParameter:\n    Type: String\n    Default: \"daae9784-6d96-11eb-a28b-38f9d33cfd0f\"\n    Description: \"Enter default api key value to be used by api gateway for basic tier tenants\"  \n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"  \n  EventEngineParameter:\n    Type: String\n    Default: false    \n    Description: \"Tells if this workshop is running as a part of AWS Event through AWS EventEngine or not\"\n  AdminUserPoolCallbackURLParameter: \n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Admin Management userpool call back url\"  \n  TenantUserPoolCallbackURLParameter:\n    Type: String\n    Default: \"http://example.com\"\n    Description: \"Enter Tenant Management userpool call back url\"   \nConditions:\n  IsNotRunningInEventEngine: !Not [ !Equals [ !Ref EventEngineParameter, true] ]\nResources:\n  DynamoDBTables:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/tables.yaml\n\n  #Create cloudfront and s3 for UI Cde\n  UserInterface:\n    Type: AWS::Serverless::Application\n    Properties:\n      Location: nested_templates/userinterface.yaml\n      Parameters:\n        IsCloudFrontAndS3PreProvisioned: !Ref EventEngineParameter\n      \n  Cognito:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/cognito.yaml\n      Parameters:\n        AdminEmailParameter: !Ref AdminEmailParameter\n        SystemAdminRoleNameParameter: !Ref SystemAdminRoleNameParameter\n        AdminUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.AdminAppSite, !Ref AdminUserPoolCallbackURLParameter]\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n  LambdaFunctions:\n    Type: AWS::Serverless::Application\n    DependsOn: UserInterface\n    Properties:\n      Location: nested_templates/lambdafunctions.yaml\n      Parameters:\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId                \n        CognitoOperationUsersUserPoolId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n        CognitoOperationUsersUserPoolClientId: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n        TenantDetailsTableArn: !GetAtt DynamoDBTables.Outputs.TenantDetailsTableArn\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn\n        ApiKeyOperationUsersParameter: !Ref ApiKeyOperationUsersParameter\n        ApiKeyPlatinumTierParameter: !Ref ApiKeyPlatinumTierParameter\n        ApiKeyPremiumTierParameter: !Ref ApiKeyPremiumTierParameter\n        ApiKeyStandardTierParameter: !Ref ApiKeyStandardTierParameter\n        ApiKeyBasicTierParameter: !Ref ApiKeyBasicTierParameter\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantUserMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantUserMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        TenantUserPoolCallbackURLParameter: !If [IsNotRunningInEventEngine, !GetAtt UserInterface.Outputs.ApplicationSite, !Ref TenantUserPoolCallbackURLParameter]\n        \n        \n  APIs:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway.yaml\n      Parameters:\n        StageName: !Ref StageName\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn                  \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn \n        ApiKeyOperationUsersParameter: !Ref ApiKeyOperationUsersParameter\n        ApiKeyPlatinumTierParameter: !Ref ApiKeyPlatinumTierParameter\n        ApiKeyPremiumTierParameter: !Ref ApiKeyPremiumTierParameter\n        ApiKeyStandardTierParameter: !Ref ApiKeyStandardTierParameter\n        ApiKeyBasicTierParameter: !Ref ApiKeyBasicTierParameter \n        \n  APIGatewayLambdaPermissions:\n    Type: AWS::Serverless::Application\n    DependsOn: LambdaFunctions\n    Properties:\n      Location: nested_templates/apigateway_lambdapermissions.yaml\n      Parameters:\n        RegisterTenantLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantLambdaExecutionRoleArn          \n        TenantManagementLambdaExecutionRoleArn: !GetAtt LambdaFunctions.Outputs.TenantManagementLambdaExecutionRoleArn          \n        RegisterTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.RegisterTenantFunctionArn\n        ProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ProvisionTenantFunctionArn\n        DeProvisionTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeProvisionTenantFunctionArn\n        ActivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.ActivateTenantFunctionArn\n        GetTenantConfigFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantConfigFunctionArn\n        GetTenantsFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantsFunctionArn\n        CreateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantFunctionArn\n        GetTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.GetTenantFunctionArn          \n        DeactivateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DeactivateTenantFunctionArn          \n        UpdateTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantFunctionArn          \n        GetUsersFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUsersFunctionArn \n        GetUserFunctionArn: !GetAtt LambdaFunctions.Outputs.GetUserFunctionArn          \n        UpdateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateUserFunctionArn          \n        DisableUserFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUserFunctionArn\n        CreateTenantAdminUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateTenantAdminUserFunctionArn\n        CreateUserFunctionArn: !GetAtt LambdaFunctions.Outputs.CreateUserFunctionArn\n        DisableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.DisableUsersByTenantFunctionArn\n        EnableUsersByTenantFunctionArn: !GetAtt LambdaFunctions.Outputs.EnableUsersByTenantFunctionArn          \n        AuthorizerFunctionArn: !GetAtt LambdaFunctions.Outputs.SharedServicesAuthorizerFunctionArn         \n        AdminApiGatewayApi: !GetAtt APIs.Outputs.AdminApiGatewayApi\n  #setup custom resources\n  CustomResources:\n    Type: AWS::Serverless::Application\n    DependsOn: APIs    \n    Properties:\n      Location: nested_templates/custom_resources.yaml\n      Parameters:\n        ServerlessSaaSSettingsTableArn: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableArn  \n        ServerlessSaaSSettingsTableName: !GetAtt DynamoDBTables.Outputs.ServerlessSaaSSettingsTableName\n        TenantStackMappingTableArn: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableArn\n        TenantStackMappingTableName: !GetAtt DynamoDBTables.Outputs.TenantStackMappingTableName\n        UpdateSettingsTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateSettingsTableFunctionArn\n        UpdateTenantStackMapTableFunctionArn: !GetAtt LambdaFunctions.Outputs.UpdateTenantStackMapTableFunctionArn\n        CognitoUserPoolId: !GetAtt Cognito.Outputs.CognitoUserPoolId\n        CognitoUserPoolClientId: !GetAtt Cognito.Outputs.CognitoUserPoolClientId      \n\nOutputs:\n  AdminApi:\n    Description: \"API Gateway endpoint URL for Admin API\"\n    Value: !Join [\"\", [\"https://\", !GetAtt APIs.Outputs.AdminApiGatewayApi, \".execute-api.\", !Ref \"AWS::Region\", \".amazonaws.com/\", !Ref StageName]]\n  AdminSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant administration application\n    Value: !GetAtt UserInterface.Outputs.AdminBucket\n    Condition: IsNotRunningInEventEngine\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt UserInterface.Outputs.AdminAppSite\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the landing application\n    Value: !GetAtt UserInterface.Outputs.LandingAppBucket\n    Condition: IsNotRunningInEventEngine\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt UserInterface.Outputs.LandingApplicationSite\n    Condition: IsNotRunningInEventEngine\n  ApplicationSiteBucket:\n    Description: The S3 Bucket that will contain the static assets for the tenant application\n    Value: !GetAtt UserInterface.Outputs.AppBucket\n    Condition: IsNotRunningInEventEngine\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt UserInterface.Outputs.ApplicationSite\n    Condition: IsNotRunningInEventEngine\n  CognitoOperationUsersUserPoolId:\n    Description: The user pool id of Admin Management userpool \n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolId\"  \n  CognitoOperationUsersUserPoolProviderURL:\n    Description: The Admin Management userpool provider url\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolProviderURL\n  CognitoOperationUsersUserPoolClientId:\n    Description: The Admin Management userpool client id\n    Value: !GetAtt Cognito.Outputs.CognitoOperationUsersUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoOperationUsersUserPoolClientId\"\n  CognitoTenantUserPoolId:\n    Description: The user pool id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantUserPoolId\"  \n  CognitoTenantAppClientId:\n    Description: The app client id for tenant user pool\n    Value: !GetAtt Cognito.Outputs.CognitoUserPoolClientId\n    Export:\n      Name: \"Serverless-SaaS-CognitoTenantAppClientId\"  \n  AuthorizerExecutionRoleArn:\n    Description: The Lambda authorizer execution role\n    Value: !GetAtt LambdaFunctions.Outputs.AuthorizerExecutionRoleArn \n    Export:\n      Name: \"Serverless-SaaS-AuthorizerExecutionRoleArn\"   \n  UsagePlanBasicTier: \n    Description: The basic tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanBasicTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanBasicTier\"\n  UsagePlanStandardTier: \n    Description: The standard tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanStandardTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanStandardTier\"\n  UsagePlanPremiumTier: \n    Description: The premium tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanPremiumTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanPremiumTier\"\n  UsagePlanPlatinumTier: \n    Description: The premium tier usage plan\n    Value: !GetAtt APIs.Outputs.UsagePlanPlatinumTier\n    Export:\n      Name: \"Serverless-SaaS-UsagePlanPlatinumTier\"\n  ApiKeyOperationUsers:\n    Description: The api key of system admins\n    Value: !Ref ApiKeyOperationUsersParameter\n    Export:\n      Name: \"Serverless-SaaS-ApiKeyOperationUsers\"    \n          "
  },
  {
    "path": "Solution/Lab6/server/tenant-buildspec.yml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nversion: 0.2\nphases:\n  install:    \n    runtime-versions:\n      python: 3.9\n    commands:\n      # Install packages or any pre-reqs in this phase.\n      # Upgrading SAM CLI to 1.33.0 version\n      - python -m pip install aws-sam-cli==1.33.0\n      - sam --version\n      # Installing project dependencies\n      - cd Lab6/server/ProductService\n      - python -m pip install -r requirements.txt \n      - cd ../OrderService\n      - python -m pip install -r requirements.txt \n      \n\n  pre_build:\n    commands:\n      # Run tests, lint scripts or any other pre-build checks.\n      - cd ..\n      - export PYTHONPATH=./ProductService/\n      # unit tests needs to be fixed. Commenting for now\n      #- python -m pytest tests/unit/ProductService-test_handler.py\n\n  build:\n    commands:\n      # Use Build phase to build your artifacts (compile, etc.)\n      - sam build -t tenant-template.yaml\n\n  post_build:\n    commands:\n      # Use Post-Build for notifications, git tags, upload artifacts to S3\n      - sam package --s3-bucket $PACKAGE_BUCKET --output-template-file packaged.yaml\n\nartifacts:\n  discard-paths: yes\n  files:\n    # List of local artifacts that will be passed down the pipeline\n    - Lab6/server/packaged.yaml"
  },
  {
    "path": "Solution/Lab6/server/tenant-samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"stack-pooled\"\ns3_bucket = \"aws-saas-sam-cli-ujwbuket\"\ns3_prefix = \"serverless-saas-tenant\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM\"\n\n"
  },
  {
    "path": "Solution/Lab6/server/tenant-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS Reference Architecture \n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n      - !Sub \"arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\"\n    Environment:\n        Variables:\n          LOG_LEVEL: DEBUG         \n          POWERTOOLS_METRICS_NAMESPACE: \"ServerlessSaaS\"\n          \nParameters:\n  TenantIdParameter:\n    Type: String\n    Default: pooled\n    Description: Tenant ID for the stack\n  StageName:\n    Type: String\n    Default: \"prod\"\n    Description: \"Stage Name for the api\"\n  LambdaReserveConcurrency:\n    Type: Number\n    Default: 20\n    Description: \"Reserve concurrency for lambda function. You can customize this on per tenant basis, if needed, by storing in the tenant table\"\nConditions:\n  IsPooledDeploy: !Equals [ !Ref TenantIdParameter, pooled]\n  IsSiloDeploy: !Not [!Equals [ !Ref TenantIdParameter, pooled]]     \nResources: \n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: !Join ['-', [serverless-saas-dependencies, !Ref TenantIdParameter]]\n      Description: Utilities for project\n      ContentUri: layers/\n      CompatibleRuntimes:\n        - python3.9          \n      LicenseInfo: 'MIT'\n      RetentionPolicy: Retain      \n    Metadata:\n      BuildMethod: python3.9   \n  \n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S          \n        - AttributeName: productId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH  \n        - AttributeName: productId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Product, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties: \n      AttributeDefinitions:\n        - AttributeName: shardId\n          AttributeType: S \n        - AttributeName: orderId\n          AttributeType: S          \n      KeySchema:\n        - AttributeName: shardId\n          KeyType: HASH \n        - AttributeName: orderId\n          KeyType: RANGE  \n      ProvisionedThroughput: \n        ReadCapacityUnits: 5\n        WriteCapacityUnits: 5\n      TableName: !Join ['-', [Order, !Ref TenantIdParameter]]\n      Tags:\n        - Key: \"TenantId\"\n          Value: !Ref TenantIdParameter\n\n  ProductFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, product-function-policy]]\n      Roles: \n        - !Ref ProductFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt ProductTable.Arn\n\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, product-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n             \n  GetProductFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_product\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable      \n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetProductsFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.get_products\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.create_product\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.update_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole \n    Properties:\n      CodeUri: ProductService/\n      Handler: product_service.delete_product\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt ProductFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"ProductService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          PRODUCT_TABLE_NAME: !Ref ProductTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  OrderFunctionExecutionRolePolicy:\n    Condition: IsSiloDeploy\n    Type: AWS::IAM::Policy\n    Properties:\n      PolicyName: !Join ['-', [!Ref TenantIdParameter, order-function-policy]]\n      Roles: \n        - !Ref OrderFunctionExecutionRole\n      PolicyDocument:\n        Version: 2012-10-17\n        Statement:              \n          - Effect: Allow\n            Action:\n              - dynamodb:GetItem\n              - dynamodb:UpdateItem\n              - dynamodb:PutItem\n              - dynamodb:DeleteItem\n              - dynamodb:Query\n            Resource:\n              - !GetAtt OrderTable.Arn\n\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, order-function-execution-role]]\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy    \n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n                \n  GetOrdersFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_orders\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  GetOrderFunction:\n    Type: AWS::Serverless::Function \n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.get_order\n      Runtime: python3.9  \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn\n      ReservedConcurrentExecutions: !If [IsPooledDeploy, !Ref \"AWS::NoValue\" , !Ref LambdaReserveConcurrency]\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.create_order\n      Runtime: python3.9  \n      Tracing: Active \n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.update_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n\n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole \n    Properties:\n      CodeUri: OrderService/\n      Handler: order_service.delete_order\n      Runtime: python3.9 \n      Tracing: Active\n      Role: !GetAtt OrderFunctionExecutionRole.Arn \n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: \"OrderService\"\n          IS_POOLED_DEPLOY: !If [IsPooledDeploy, true, false]\n          ORDER_TABLE_NAME: !Ref OrderTable\n      Tags:\n        TenantId: !Ref TenantIdParameter\n        \n  BusinessServicesAuthorizerFunction:\n    Type: AWS::Serverless::Function \n    Properties:\n      CodeUri: Resources/\n      Handler: tenant_authorizer.lambda_handler\n      Runtime: python3.9\n      Role: !ImportValue Serverless-SaaS-AuthorizerExecutionRoleArn\n      MemorySize: 256\n      Tracing: Active\n      Layers: \n        - !Ref ServerlessSaaSLayers\n      Environment:\n        Variables:\n          OPERATION_USERS_USER_POOL: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolId\n          OPERATION_USERS_APP_CLIENT: !ImportValue Serverless-SaaS-CognitoOperationUsersUserPoolClientId\n          OPERATION_USERS_API_KEY : !ImportValue Serverless-SaaS-ApiKeyOperationUsers\n          \n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: !Join ['-', [/aws/api-gateway/access-logs-serverless-saas-tenant-api-, !Ref TenantIdParameter]]\n      RetentionInDays: 30\n  ThrottlingLimitMetricFilter:\n    Type: AWS::Logs::MetricFilter\n    Properties:\n      LogGroupName: \n        Ref: \"ApiGatewayAccessLogs\"\n      FilterPattern: '{$.status = \"429\"}'\n      MetricTransformations:\n        - \n          MetricValue: \"1\"\n          MetricNamespace: \"Serverless-SaaS-Reference-Architecture\"\n          MetricName: !Join ['-', [\"ThrottlingLimitExceeded\", !Ref TenantIdParameter]]\n  ThrottlingLimitExceeded:\n    Type: AWS::CloudWatch::Alarm\n    Properties:\n      AlarmDescription: Throttling limit exceeded errors\n      ComparisonOperator: GreaterThanThreshold\n      EvaluationPeriods: 1\n      MetricName: !Join ['-', [\"ThrottlingLimitExceeded\", !Ref TenantIdParameter]]\n      Namespace: \"Serverless-SaaS-Reference-Architecture\"\n      Period: 60\n      Statistic: SampleCount\n      Threshold: 0 \n  ApiGatewayTenantApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n        - DataTraceEnabled: False\n          LoggingLevel: INFO\n          MetricsEnabled: True\n          ResourcePath: '/*' \n          HttpMethod: '*' \n      AccessLogSetting:\n        DestinationArn: !GetAtt ApiGatewayAccessLogs.Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\", \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\", \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\" }'\n      TracingEnabled: True\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: !Join ['-', [!Ref TenantIdParameter, 'serverless-saas-tenant-api']]\n        basePath: !Join ['', ['/', !Ref StageName]]\n        x-amazon-apigateway-api-key-source : \"AUTHORIZER\"\n        schemes:\n          - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - api_key: []      \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []   \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:      \n                - api_key: [] \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []  \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetOrdersFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                              \n          /order:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:    \n                - api_key: []     \n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateOrderFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock     \n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:  \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            put:              \n              produces:\n                - application/json\n              responses: {}\n              security: \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt UpdateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy     \n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n                - application/json\n              parameters:\n                - name: id\n                  in: path\n                  required: true\n                  type: string\n              responses: {}\n              security:     \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt DeleteProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock      \n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n                - application/json\n              responses: {}\n              security: \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt GetProductsFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy  \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                           \n          /product:\n            post:              \n              produces:\n                - application/json\n              responses: {}\n              security:   \n                - api_key: []\n                - Authorizer: []\n              x-amazon-apigateway-integration:\n                uri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    -  !GetAtt CreateProductFunction.Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy   \n            options:\n              consumes:\n                - application/json\n              produces:\n                - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: \"#/definitions/Empty\"\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: \"'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'\"\n                      method.response.header.Access-Control-Allow-Headers: \"'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'\"\n                      method.response.header.Access-Control-Allow-Origin:  \"'*'\"\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: \"{\\\"statusCode\\\": 200}\"\n                type: mock                                    \n        components:\n          securitySchemes:                    \n            Authorizer:\n              type: \"apiKey\"\n              name: \"Authorization\"\n              in: \"header\"\n              x-amazon-apigateway-authtype: \"custom\"\n              x-amazon-apigateway-authorizer:\n                authorizerUri: !Join\n                  - ''\n                  - - !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - !GetAtt BusinessServicesAuthorizerFunction.Arn                      \n                    - /invocations\n                authorizerResultTtlInSeconds: 30\n                type: \"token\"\n      StageName: !Ref StageName\n  \n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt GetProductsFunction.Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ] \n      \n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  \n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]      \n\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]  \n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]    \n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]         \n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt \n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\n        \"\", [\n          \"arn:aws:execute-api:\", \n          {\"Ref\": \"AWS::Region\"}, \":\", \n          {\"Ref\": \"AWS::AccountId\"}, \":\", \n          !Ref ApiGatewayTenantApi, \"/*/*/*\"\n          ]\n        ]            \n\n  AuthorizerLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName: !GetAtt BusinessServicesAuthorizerFunction.Arn     \n      Principal: apigateway.amazonaws.com\n      SourceArn: !Join [\"\", [\"arn:aws:execute-api:\", !Ref \"AWS::Region\", \":\", !Ref \"AWS::AccountId\", \":\", !Ref ApiGatewayTenantApi, \"/*/*\" ]]\n\n  UpdateUsagePlanLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    DependsOn: ApiGatewayTenantApi\n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, update-usage-plan-role]]\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n      Policies:\n        - PolicyName: !Join ['-', [!Ref TenantIdParameter, update-usage-plan-policy]]\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - kms:Decrypt\n                Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*\n              - Effect: Allow\n                Action:\n                  - logs:CreateLogGroup\n                  - logs:PutLogEvents\n                  - logs:CreateLogStream\n                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*\n              - Effect: Allow\n                Action:\n                  - logs:DescribeLogStreams\n                Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*\n              - Effect: Allow\n                Action:\n                  - xray:PutTraceSegments\n                  - xray:PutTelemetryRecords\n                Resource: \"*\"\n              - Effect: Allow\n                Action:\n                  - apigateway:PATCH\n                Resource: !Sub arn:aws:apigateway:${AWS::Region}::/usageplans/* \n              - Effect: Allow\n                Action:\n                  - dynamodb:GetItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-Settings\n  UpdateUsagePlanFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateUsagePlanLambdaExecutionRole\n    Properties:\n      CodeUri: custom_resources/\n      Handler: update_usage_plan.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateUsagePlanLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  AssociateUsagePlanWithTenantAPI:\n    Type: Custom::AssociateUsagePlanWithTenantAPI\n    DependsOn: UpdateUsagePlanFunction\n    Properties:\n      ServiceToken: !GetAtt UpdateUsagePlanFunction.Arn\n      ApiGatewayId: !Ref ApiGatewayTenantApi\n      SettingsTableName: ServerlessSaaS-Settings\n      IsPooledDeploy: !If [IsPooledDeploy, true, false]     \n      Stage: !Ref StageName\n      UsagePlanBasicTier: !ImportValue Serverless-SaaS-UsagePlanBasicTier\n      UsagePlanStandardTier: !ImportValue Serverless-SaaS-UsagePlanStandardTier\n      UsagePlanPremiumTier: !ImportValue Serverless-SaaS-UsagePlanPremiumTier\n      UsagePlanPlatinumTier: !ImportValue Serverless-SaaS-UsagePlanPlatinumTier    \n  UpdateTenantApiGatewayUrlLambdaExecutionRole:\n    Type: AWS::IAM::Role\n    DependsOn: ApiGatewayTenantApi\n    Properties:\n      RoleName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exec-role]]\n      Path: \"/\"\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n        - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n        - PolicyName: !Join ['-', [!Ref TenantIdParameter, apigwurl-lambda-exe-policy ]]\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - dynamodb:PutItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-Settings \n              - Effect: Allow\n                Action:\n                  - dynamodb:UpdateItem\n                Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/ServerlessSaaS-TenantDetails    \n  UpdateTenantApiGatewayUrlFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: UpdateTenantApiGatewayUrlLambdaExecutionRole\n    Properties:\n      CodeUri: custom_resources/\n      Handler: update_tenant_apigatewayurl.handler\n      Runtime: python3.9\n      Role: !GetAtt UpdateTenantApiGatewayUrlLambdaExecutionRole.Arn\n      Layers: \n          - !Ref ServerlessSaaSLayers\n  UpdateTenantApiGatewayUrl:\n    Type: Custom::UpdateTenantApiGatewayUrl\n    DependsOn: UpdateTenantApiGatewayUrlFunction\n    Properties:\n      ServiceToken: !GetAtt UpdateTenantApiGatewayUrlFunction.Arn\n      TenantDetailsTableName: ServerlessSaaS-TenantDetails\n      SettingsTableName: ServerlessSaaS-Settings  \n      TenantId: !Ref TenantIdParameter  \n      TenantApiGatewayUrl: !Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/prod/\"    \n  \nOutputs:\n  TenantApiGatewayId:\n    Description: Id for Tenant API Gateway\n    Value: !Ref ApiGatewayTenantApi\n  TenantAPI:\n    Description: \"API Gateway endpoint URL for Tenant API\"\n    Value: !Join ['', [!Sub \"https://${ApiGatewayTenantApi}.execute-api.${AWS::Region}.amazonaws.com/\", !Ref StageName]]"
  },
  {
    "path": "Solution/Lab7/.aws-sam/build/GetDynamoDBUsageAndCostByTenant/requirements.txt",
    "content": ""
  },
  {
    "path": "Solution/Lab7/.aws-sam/build/GetDynamoDBUsageAndCostByTenant/tenant_usage_and_cost.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport boto3\nimport time\nimport os\nfrom datetime import datetime, timedelta\nfrom botocore.exceptions import ClientError\nfrom decimal import *\n\ncloudformation = boto3.client('cloudformation')\nlogs = boto3.client('logs')\nathena = boto3.client('athena')\ndynamodb = boto3.resource('dynamodb')\nattribution_table = dynamodb.Table(\"TenantCostAndUsageAttribution\")\n\nATHENA_S3_OUTPUT = os.getenv(\"ATHENA_S3_OUTPUT\")\nRETRY_COUNT = 100\n\n#This function needs to be scheduled on daily basis\ndef calculate_daily_dynamodb_attribution_by_tenant(event, context):\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_dynamodb_cost = __get_total_service_cost('AmazonDynamoDB', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields tenant_id as TenantId, service as Service, \\\n     ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by TenantId, dateceil(@timestamp, 1d) as timestamp'\n\n    print( log_group_names)\n    \n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day)    \n    #optionally save this data in a table\n    \n    total_usage_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day)  \n    \n    total_RCU = 0 \n    total_WCU = 0 \n    for result in total_usage_by_day['results'][0]:\n        if 'ReadCapacityUnits' in result['field']:\n            total_RCU = Decimal(result['value'])\n        if 'WriteCapacityUnits' in result['field']:\n            total_WCU = Decimal(result['value'])\n    \n    print (total_RCU)\n    print (total_WCU)\n    \n    if (total_RCU + total_WCU > 0):\n        total_RCU_By_Tenant = 0\n        total_WCU_By_Tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'ReadCapacityUnits' in field['field']:\n                    total_RCU_By_Tenant = Decimal(field['value'])\n                if 'WriteCapacityUnits' in field['field']:\n                    total_WCU_By_Tenant = Decimal(field['value'])\n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (((total_RCU_By_Tenant * 5) + total_WCU_By_Tenant) / ((total_RCU * 5) + total_WCU)) \n            tenant_dynamodb_cost = tenant_attribution_percentage * total_dynamodb_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"DynamoDB\",\n                            \"TenantId\": tenant_id, \n                            \"TotalRCU\": total_RCU, \n                            \"TenantTotalRCU\": total_RCU_By_Tenant, \n                            \"TotalWCU\": total_WCU,\n                            \"TenantTotalWCU\": total_WCU_By_Tenant, \n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_dynamodb_cost,\n                            \"TotalServiceCost\": total_dynamodb_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n                \n            tenant_id = 'unknown'\n            total_RCU_By_Tenant = 0.0\n            total_WCU_By_Tenant = 0.0\n        \n    \n    \n#Below function considers number of invocation as the metrics to calculate usage and cost. \n#You can go granluar by recording duration of each metrics and use that to get more granular\n#Since our functions are basic CRUD this might work as a ball park cost estimate\ndef calculate_daily_lambda_attribution_by_tenant(event, context):\n    \n    #Get total dynamodb cost for the given duration\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_lambda_cost = __get_total_service_cost('AWSLambda', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query='fields @timestamp, @message \\\n        | filter @message like /Request completed/ \\\n        | fields tenant_id as TenantId , CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by TenantId, dateceil(@timestamp, 1d) as timestamp'\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day) \n\n    total_usage_by_day_query = 'filter @message like /Request completed/ \\\n        | fields CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day) \n    \n    total_invocations = 1 #to avoid divide by zero\n    for result in total_usage_by_day['results'][0]:\n        if 'LambdaInvocations' in result['field']:\n            total_invocations = Decimal(result['value'])\n        \n    \n    print (total_invocations)\n    \n    if (total_invocations>0):\n        total_invocations_by_tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'LambdaInvocations' in field['field']:\n                    total_invocations_by_tenant = Decimal(field['value'])\n                \n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (total_invocations_by_tenant / total_invocations) \n            tenant_lambda_cost = tenant_attribution_percentage * total_lambda_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"AWSLambda\",\n                            \"TenantId\": tenant_id, \n                            \"TotalInvocations\": total_invocations, \n                            \"TenantTotalInvocations\": total_invocations_by_tenant,\n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_lambda_cost,\n                            \"TotalServiceCost\": total_lambda_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n            \n            tenant_id = 'unknown'\n            tenant_total_RCU = 0.0\n            tenant_total_WCU = 0.0\n\ndef __get_total_service_cost(servicename, start_date_time, end_date_time):\n\n    # We need to add more filters for day, month, year, resource ids etc. Below query is because we are just using a sample cur file\n    #Ignoting startTime and endTime filter for now since we have a static/sample cur file\n    \n    query = \"SELECT sum(line_item_blended_cost) AS cost FROM costexplorerdb.curoutput WHERE line_item_product_code='{0}'\".format(servicename) \n\n    # Execution\n    response = athena.start_query_execution(\n        QueryString=query,\n        QueryExecutionContext={\n            'Database': 'costexplorerdb'\n        },\n        ResultConfiguration={\n            'OutputLocation': \"s3://\" + ATHENA_S3_OUTPUT,\n        }\n    )\n\n    # get query execution id\n    query_execution_id = response['QueryExecutionId']\n    print(query_execution_id)\n\n    # get execution status\n    for i in range(1, 1 + RETRY_COUNT):\n\n        # get query execution\n        query_status = athena.get_query_execution(QueryExecutionId=query_execution_id)\n        print (query_status)\n        query_execution_status = query_status['QueryExecution']['Status']['State']\n\n        if query_execution_status == 'SUCCEEDED':\n            print(\"STATUS:\" + query_execution_status)\n            break\n\n        if query_execution_status == 'FAILED':\n            raise Exception(\"STATUS:\" + query_execution_status)\n\n        else:\n            print(\"STATUS:\" + query_execution_status)\n            time.sleep(i)\n    else:\n        athena.stop_query_execution(QueryExecutionId=query_execution_id)\n        raise Exception('TIME OVER')\n\n    # get query results\n    result = athena.get_query_results(QueryExecutionId=query_execution_id)\n    \n    print (result)\n    \n    \n    total_dynamo_db_cost = result['ResultSet']['Rows'][1]['Data'][0]['VarCharValue']\n    print(total_dynamo_db_cost)\n    \n    return Decimal(total_dynamo_db_cost)\n    \ndef __query_cloudwatch_logs(logs, log_group_names, query_string, start_time, end_time):\n    query = logs.start_query(logGroupNames=log_group_names,\n    startTime=start_time,\n    endTime=end_time,\n    queryString=query_string)\n\n    query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    while query_results['status']=='Running' or query_results['status']=='Scheduled':\n        time.sleep(5)\n        query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    return query_results\n\ndef __is_log_group_exists(logs_client, log_group_name):\n    \n    logs_paginator = logs_client.get_paginator('describe_log_groups')\n    response_iterator = logs_paginator.paginate(logGroupNamePrefix=log_group_name)\n    for log_groups_list in response_iterator:\n        if not log_groups_list[\"logGroups\"]:\n            return False\n        else:\n            return True       \n\ndef __add_log_group_name(logs_client, log_group_name, log_group_names_list):\n\n    if __is_log_group_exists(logs_client, log_group_name):\n        log_group_names_list.append(log_group_name)\n\n\ndef __get_list_of_log_group_names():\n    log_group_names = []\n    log_group_prefix = '/aws/lambda/'\n    cloudformation_paginator = cloudformation.get_paginator('list_stack_resources')\n    response_iterator = cloudformation_paginator.paginate(StackName='stack-pooled')\n    for stack_resources in response_iterator:\n        for resource in stack_resources['StackResourceSummaries']:\n            if (resource[\"LogicalResourceId\"] == \"CreateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue    \n            if (resource[\"LogicalResourceId\"] == \"UpdateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetProductsFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue         \n            if (resource[\"LogicalResourceId\"] == \"CreateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"UpdateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetOrdersFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue \n\n    return log_group_names                     \n\n"
  },
  {
    "path": "Solution/Lab7/.aws-sam/build/GetLambdaUsageAndCostByTenant/requirements.txt",
    "content": ""
  },
  {
    "path": "Solution/Lab7/.aws-sam/build/GetLambdaUsageAndCostByTenant/tenant_usage_and_cost.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport boto3\nimport time\nimport os\nfrom datetime import datetime, timedelta\nfrom botocore.exceptions import ClientError\nfrom decimal import *\n\ncloudformation = boto3.client('cloudformation')\nlogs = boto3.client('logs')\nathena = boto3.client('athena')\ndynamodb = boto3.resource('dynamodb')\nattribution_table = dynamodb.Table(\"TenantCostAndUsageAttribution\")\n\nATHENA_S3_OUTPUT = os.getenv(\"ATHENA_S3_OUTPUT\")\nRETRY_COUNT = 100\n\n#This function needs to be scheduled on daily basis\ndef calculate_daily_dynamodb_attribution_by_tenant(event, context):\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_dynamodb_cost = __get_total_service_cost('AmazonDynamoDB', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields tenant_id as TenantId, service as Service, \\\n     ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by TenantId, dateceil(@timestamp, 1d) as timestamp'\n\n    print( log_group_names)\n    \n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day)    \n    #optionally save this data in a table\n    \n    total_usage_by_day_query = 'fields @timestamp, @message \\\n    | filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day)  \n    \n    total_RCU = 0 \n    total_WCU = 0 \n    for result in total_usage_by_day['results'][0]:\n        if 'ReadCapacityUnits' in result['field']:\n            total_RCU = Decimal(result['value'])\n        if 'WriteCapacityUnits' in result['field']:\n            total_WCU = Decimal(result['value'])\n    \n    print (total_RCU)\n    print (total_WCU)\n    \n    if (total_RCU + total_WCU > 0):\n        total_RCU_By_Tenant = 0\n        total_WCU_By_Tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'ReadCapacityUnits' in field['field']:\n                    total_RCU_By_Tenant = Decimal(field['value'])\n                if 'WriteCapacityUnits' in field['field']:\n                    total_WCU_By_Tenant = Decimal(field['value'])\n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (((total_RCU_By_Tenant * 5) + total_WCU_By_Tenant) / ((total_RCU * 5) + total_WCU)) \n            tenant_dynamodb_cost = tenant_attribution_percentage * total_dynamodb_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"DynamoDB\",\n                            \"TenantId\": tenant_id, \n                            \"TotalRCU\": total_RCU, \n                            \"TenantTotalRCU\": total_RCU_By_Tenant, \n                            \"TotalWCU\": total_WCU,\n                            \"TenantTotalWCU\": total_WCU_By_Tenant, \n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_dynamodb_cost,\n                            \"TotalServiceCost\": total_dynamodb_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n                \n            tenant_id = 'unknown'\n            total_RCU_By_Tenant = 0.0\n            total_WCU_By_Tenant = 0.0\n        \n    \n    \n#Below function considers number of invocation as the metrics to calculate usage and cost. \n#You can go granluar by recording duration of each metrics and use that to get more granular\n#Since our functions are basic CRUD this might work as a ball park cost estimate\ndef calculate_daily_lambda_attribution_by_tenant(event, context):\n    \n    #Get total dynamodb cost for the given duration\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_lambda_cost = __get_total_service_cost('AWSLambda', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query='fields @timestamp, @message \\\n        | filter @message like /Request completed/ \\\n        | fields tenant_id as TenantId , CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by TenantId, dateceil(@timestamp, 1d) as timestamp'\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day) \n\n    total_usage_by_day_query = 'filter @message like /Request completed/ \\\n        | fields CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day) \n    \n    total_invocations = 1 #to avoid divide by zero\n    for result in total_usage_by_day['results'][0]:\n        if 'LambdaInvocations' in result['field']:\n            total_invocations = Decimal(result['value'])\n        \n    \n    print (total_invocations)\n    \n    if (total_invocations>0):\n        total_invocations_by_tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'LambdaInvocations' in field['field']:\n                    total_invocations_by_tenant = Decimal(field['value'])\n                \n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (total_invocations_by_tenant / total_invocations) \n            tenant_lambda_cost = tenant_attribution_percentage * total_lambda_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"ServiceName\": \"AWSLambda\",\n                            \"TenantId\": tenant_id, \n                            \"TotalInvocations\": total_invocations, \n                            \"TenantTotalInvocations\": total_invocations_by_tenant,\n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_lambda_cost,\n                            \"TotalServiceCost\": total_lambda_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n            \n            tenant_id = 'unknown'\n            tenant_total_RCU = 0.0\n            tenant_total_WCU = 0.0\n\ndef __get_total_service_cost(servicename, start_date_time, end_date_time):\n\n    # We need to add more filters for day, month, year, resource ids etc. Below query is because we are just using a sample cur file\n    #Ignoting startTime and endTime filter for now since we have a static/sample cur file\n    \n    query = \"SELECT sum(line_item_blended_cost) AS cost FROM costexplorerdb.curoutput WHERE line_item_product_code='{0}'\".format(servicename) \n\n    # Execution\n    response = athena.start_query_execution(\n        QueryString=query,\n        QueryExecutionContext={\n            'Database': 'costexplorerdb'\n        },\n        ResultConfiguration={\n            'OutputLocation': \"s3://\" + ATHENA_S3_OUTPUT,\n        }\n    )\n\n    # get query execution id\n    query_execution_id = response['QueryExecutionId']\n    print(query_execution_id)\n\n    # get execution status\n    for i in range(1, 1 + RETRY_COUNT):\n\n        # get query execution\n        query_status = athena.get_query_execution(QueryExecutionId=query_execution_id)\n        print (query_status)\n        query_execution_status = query_status['QueryExecution']['Status']['State']\n\n        if query_execution_status == 'SUCCEEDED':\n            print(\"STATUS:\" + query_execution_status)\n            break\n\n        if query_execution_status == 'FAILED':\n            raise Exception(\"STATUS:\" + query_execution_status)\n\n        else:\n            print(\"STATUS:\" + query_execution_status)\n            time.sleep(i)\n    else:\n        athena.stop_query_execution(QueryExecutionId=query_execution_id)\n        raise Exception('TIME OVER')\n\n    # get query results\n    result = athena.get_query_results(QueryExecutionId=query_execution_id)\n    \n    print (result)\n    \n    \n    total_dynamo_db_cost = result['ResultSet']['Rows'][1]['Data'][0]['VarCharValue']\n    print(total_dynamo_db_cost)\n    \n    return Decimal(total_dynamo_db_cost)\n    \ndef __query_cloudwatch_logs(logs, log_group_names, query_string, start_time, end_time):\n    query = logs.start_query(logGroupNames=log_group_names,\n    startTime=start_time,\n    endTime=end_time,\n    queryString=query_string)\n\n    query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    while query_results['status']=='Running' or query_results['status']=='Scheduled':\n        time.sleep(5)\n        query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    return query_results\n\ndef __is_log_group_exists(logs_client, log_group_name):\n    \n    logs_paginator = logs_client.get_paginator('describe_log_groups')\n    response_iterator = logs_paginator.paginate(logGroupNamePrefix=log_group_name)\n    for log_groups_list in response_iterator:\n        if not log_groups_list[\"logGroups\"]:\n            return False\n        else:\n            return True       \n\ndef __add_log_group_name(logs_client, log_group_name, log_group_names_list):\n\n    if __is_log_group_exists(logs_client, log_group_name):\n        log_group_names_list.append(log_group_name)\n\n\ndef __get_list_of_log_group_names():\n    log_group_names = []\n    log_group_prefix = '/aws/lambda/'\n    cloudformation_paginator = cloudformation.get_paginator('list_stack_resources')\n    response_iterator = cloudformation_paginator.paginate(StackName='stack-pooled')\n    for stack_resources in response_iterator:\n        for resource in stack_resources['StackResourceSummaries']:\n            if (resource[\"LogicalResourceId\"] == \"CreateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue    \n            if (resource[\"LogicalResourceId\"] == \"UpdateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetProductsFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue         \n            if (resource[\"LogicalResourceId\"] == \"CreateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"UpdateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetOrdersFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue \n\n    return log_group_names                     \n\n"
  },
  {
    "path": "Solution/Lab7/.aws-sam/build/template.yaml",
    "content": "AWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: 'Serverless SaaS - Cost by tenant\n\n  '\nGlobals:\n  Function:\n    Timeout: 29\nResources:\n  CURBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy: Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n        - ServerSideEncryptionByDefault:\n            SSEAlgorithm: AES256\n      PublicAccessBlockConfiguration:\n        BlockPublicAcls: true\n        BlockPublicPolicy: true\n        IgnorePublicAcls: true\n        RestrictPublicBuckets: true\n  AWSCURDatabase:\n    Type: AWS::Glue::Database\n    Properties:\n      DatabaseInput:\n        Name:\n          Fn::Sub: costexplorerdb\n      CatalogId:\n        Ref: AWS::AccountId\n  AWSCURCrawlerComponentFunction:\n    Type: AWS::IAM::Role\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - glue.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      Path: /\n      ManagedPolicyArns:\n      - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole\n      Policies:\n      - PolicyName: AWSCURCrawlerComponentFunction\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - logs:CreateLogGroup\n            - logs:CreateLogStream\n            - logs:PutLogEvents\n            Resource:\n              Fn::Sub: arn:${AWS::Partition}:logs:*:*:*\n          - Effect: Allow\n            Action:\n            - glue:UpdateDatabase\n            - glue:UpdatePartition\n            - glue:CreateTable\n            - glue:UpdateTable\n            - glue:ImportCatalogToGlue\n            Resource: '*'\n          - Effect: Allow\n            Action:\n            - s3:GetObject\n            - s3:PutObject\n            Resource:\n              Fn::Sub: ${CURBucket.Arn}*\n      - PolicyName: AWSCURKMSDecryption\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - kms:Decrypt\n            Resource: '*'\n  AWSCURCrawlerLambdaExecutor:\n    Type: AWS::IAM::Role\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - lambda.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      Path: /\n      Policies:\n      - PolicyName: AWSCURCrawlerLambdaExecutor\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - logs:CreateLogGroup\n            - logs:CreateLogStream\n            - logs:PutLogEvents\n            Resource:\n              Fn::Sub: arn:${AWS::Partition}:logs:*:*:*\n          - Effect: Allow\n            Action:\n            - glue:StartCrawler\n            Resource: '*'\n  AWSCURCrawler:\n    Type: AWS::Glue::Crawler\n    DependsOn:\n    - AWSCURDatabase\n    - AWSCURCrawlerComponentFunction\n    Properties:\n      Name: AWSCURCrawler-Multi-tenant\n      Description: A recurring crawler that keeps your CUR table in Athena up-to-date.\n      Role:\n        Fn::GetAtt:\n        - AWSCURCrawlerComponentFunction\n        - Arn\n      DatabaseName:\n        Ref: AWSCURDatabase\n      Targets:\n        S3Targets:\n        - Path:\n            Fn::Sub: s3://${CURBucket}/curoutput\n          Exclusions:\n          - '**.json'\n          - '**.yml'\n          - '**.sql'\n          - '**.csv'\n          - '**.gz'\n          - '**.zip'\n      SchemaChangePolicy:\n        UpdateBehavior: UPDATE_IN_DATABASE\n        DeleteBehavior: DELETE_FROM_DATABASE\n  AWSCURInitializer:\n    Type: AWS::Lambda::Function\n    DependsOn: AWSCURCrawler\n    Properties:\n      Code:\n        ZipFile: \"const AWS = require('aws-sdk'); const response = require('./cfn-response');\\\n          \\ exports.handler = function(event, context, callback) {\\n  if (event.RequestType\\\n          \\ === 'Delete') {\\n    response.send(event, context, response.SUCCESS);\\n\\\n          \\  } else {\\n    const glue = new AWS.Glue();\\n    glue.startCrawler({ Name:\\\n          \\ 'AWSCURCrawler-Multi-tenant' }, function(err, data) {\\n      if (err)\\\n          \\ {\\n        const responseData = JSON.parse(this.httpResponse.body);\\n\\\n          \\        if (responseData['__type'] == 'CrawlerRunningException') {\\n  \\\n          \\        callback(null, responseData.Message);\\n        } else {\\n     \\\n          \\     const responseString = JSON.stringify(responseData);\\n          if\\\n          \\ (event.ResponseURL) {\\n            response.send(event, context, response.FAILED,{\\\n          \\ msg: responseString });\\n          } else {\\n            callback(responseString);\\n\\\n          \\          }\\n        }\\n      }\\n      else {\\n        if (event.ResponseURL)\\\n          \\ {\\n          response.send(event, context, response.SUCCESS);\\n      \\\n          \\  } else {\\n          callback(null, response.SUCCESS);\\n        }\\n  \\\n          \\    }\\n    });\\n  }\\n};\\n\"\n      Handler: index.handler\n      Timeout: 30\n      Runtime: nodejs16.x\n      ReservedConcurrentExecutions: 1\n      Role:\n        Fn::GetAtt:\n        - AWSCURCrawlerLambdaExecutor\n        - Arn\n  TenantCostandUsageAttributionTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n      - AttributeName: Date\n        AttributeType: N\n      - AttributeName: ServiceName\n        AttributeType: S\n      KeySchema:\n      - AttributeName: Date\n        KeyType: HASH\n      - AttributeName: ServiceName\n        KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST\n      TableName: TenantCostAndUsageAttribution\n  QueryLogInsightsExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: product-function-execution-role-lab1\n      Path: /\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - lambda.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      ManagedPolicyArns:\n      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n      Policies:\n      - PolicyName: query-log-insight-lab7\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - logs:GetQueryResults\n            - logs:StartQuery\n            - logs:StopQuery\n            - logs:FilterLogEvents\n            - logs:DescribeLogGroups\n            - cloudformation:ListStackResources\n            Resource:\n            - '*'\n          - Effect: Allow\n            Action:\n            - s3:*\n            Resource:\n            - Fn::Sub: arn:aws:s3:::${CURBucket}*\n          - Effect: Allow\n            Action:\n            - dynamodb:*\n            Resource:\n            - Fn::GetAtt:\n              - TenantCostandUsageAttributionTable\n              - Arn\n          - Effect: Allow\n            Action:\n            - Athena:*\n            Resource:\n            - '*'\n          - Effect: Allow\n            Action:\n            - glue:*\n            Resource:\n            - '*'\n  GetDynamoDBUsageAndCostByTenant:\n    Type: AWS::Serverless::Function\n    DependsOn: QueryLogInsightsExecutionRole\n    Properties:\n      CodeUri: GetDynamoDBUsageAndCostByTenant\n      Handler: tenant_usage_and_cost.calculate_daily_dynamodb_attribution_by_tenant\n      Runtime: python3.9\n      Role:\n        Fn::GetAtt:\n        - QueryLogInsightsExecutionRole\n        - Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT:\n            Ref: CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateDynamoUsageAndCostByTenant\n            Schedule: rate(5 minutes)\n  GetLambdaUsageAndCostByTenant:\n    Type: AWS::Serverless::Function\n    DependsOn: QueryLogInsightsExecutionRole\n    Properties:\n      CodeUri: GetLambdaUsageAndCostByTenant\n      Handler: tenant_usage_and_cost.calculate_daily_lambda_attribution_by_tenant\n      Runtime: python3.9\n      Role:\n        Fn::GetAtt:\n        - QueryLogInsightsExecutionRole\n        - Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT:\n            Ref: CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateLambdaUsageAndCostByTenant\n            Schedule: rate(5 minutes)\nOutputs:\n  CURBucketname:\n    Description: The name of S3 bucket name\n    Value:\n      Ref: CURBucket\n    Export:\n      Name: CURBucketname\n  AWSCURInitializerFunctionName:\n    Description: Function name of CUR initializer\n    Value:\n      Ref: AWSCURInitializer\n    Export:\n      Name: AWSCURInitializerFunctionName\n"
  },
  {
    "path": "Solution/Lab7/.aws-sam/build.toml",
    "content": "# This file is auto generated by SAM CLI build command\n\n[function_build_definitions]\n[function_build_definitions.8d6cee39-9fc9-4073-9b65-22bf29298af4]\ncodeuri = \"/Users/shaanubh/Documents/code/serverless-saas-workshop/code/aws-serverless-saas-workshop/Lab7/TenantUsageAndCost\"\nruntime = \"python3.8\"\narchitecture = \"x86_64\"\nmanifest_hash = \"\"\npackagetype = \"Zip\"\nfunctions = [\"GetDynamoDBUsageAndCostByTenant\", \"GetLambdaUsageAndCostByTenant\"]\n\n[layer_build_definitions]\n"
  },
  {
    "path": "Solution/Lab7/TenantUsageAndCost/requirements.txt",
    "content": ""
  },
  {
    "path": "Solution/Lab7/TenantUsageAndCost/tenant_usage_and_cost.py",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nimport boto3\nimport time\nimport os\nfrom datetime import datetime, timedelta\nfrom botocore.exceptions import ClientError\nfrom decimal import *\n\ncloudformation = boto3.client('cloudformation')\nlogs = boto3.client('logs')\nathena = boto3.client('athena')\ndynamodb = boto3.resource('dynamodb')\nattribution_table = dynamodb.Table(\"TenantCostAndUsageAttribution\")\n\nATHENA_S3_OUTPUT = os.getenv(\"ATHENA_S3_OUTPUT\")\nRETRY_COUNT = 100\n\n#This function needs to be scheduled on daily basis\ndef calculate_daily_dynamodb_attribution_by_tenant(event, context):\n    start_date_time = __get_start_date_time() #current day epoch\n    end_date_time =  __get_end_date_time() #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_dynamodb_cost = __get_total_service_cost('AmazonDynamoDB', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    print( log_group_names)\n\n    usage_by_tenant_by_day_query = 'filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields tenant_id as TenantId, ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by TenantId, dateceil(@timestamp, 1d) as timestamp'\n\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day)    \n    \n    total_usage_by_day_query = 'filter @message like /ReadCapacityUnits|WriteCapacityUnits/ \\\n    | fields ReadCapacityUnits.0 as RCapacityUnits, WriteCapacityUnits.0 as WCapacityUnits \\\n    | stats sum(RCapacityUnits) as ReadCapacityUnits, sum(WCapacityUnits) as WriteCapacityUnits by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day)  \n    \n    total_RCU = 0 \n    total_WCU = 0 \n    for result in total_usage_by_day['results'][0]:\n        if 'ReadCapacityUnits' in result['field']:\n            total_RCU = Decimal(result['value'])\n        if 'WriteCapacityUnits' in result['field']:\n            total_WCU = Decimal(result['value'])\n    \n    print (total_RCU)\n    print (total_WCU)\n    \n    if (total_RCU + total_WCU > 0):\n        total_RCU_By_Tenant = 0\n        total_WCU_By_Tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'ReadCapacityUnits' in field['field']:\n                    total_RCU_By_Tenant = Decimal(field['value'])\n                if 'WriteCapacityUnits' in field['field']:\n                    total_WCU_By_Tenant = Decimal(field['value'])\n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (((total_RCU_By_Tenant * 5) + total_WCU_By_Tenant) / ((total_RCU * 5) + total_WCU)) \n            tenant_dynamodb_cost = tenant_attribution_percentage * total_dynamodb_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"TenantId#ServiceName\": tenant_id+\"#\"+\"DynamoDB\",\n                            \"TenantId\": tenant_id, \n                            \"TotalRCU\": total_RCU, \n                            \"TenantTotalRCU\": total_RCU_By_Tenant, \n                            \"TotalWCU\": total_WCU,\n                            \"TenantTotalWCU\": total_WCU_By_Tenant, \n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_dynamodb_cost,\n                            \"TotalServiceCost\": total_dynamodb_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n                \n            tenant_id = 'unknown'\n            total_RCU_By_Tenant = 0.0\n            total_WCU_By_Tenant = 0.0\n        \n    \n    \n#Below function considers number of invocation as the metrics to calculate usage and cost. \n#You can go granluar by recording duration of each metrics and use that to get more granular\n#Since our functions are basic CRUD this might work as a ball park cost estimate\ndef calculate_daily_lambda_attribution_by_tenant(event, context):\n    \n    #Get total dynamodb cost for the given duration\n    start_date_time = __get_start_date_time() #current day epoch\n    end_date_time =  __get_end_date_time() #next day epoch\n    \n    #Get total dynamodb cost for the given duration\n    total_lambda_cost = __get_total_service_cost('AWSLambda', start_date_time, end_date_time)\n\n    log_group_names = __get_list_of_log_group_names()\n    \n    usage_by_tenant_by_day_query='fields @timestamp, @message \\\n        | filter @message like /Request completed/ \\\n        | fields tenant_id as TenantId , CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by TenantId, dateceil(@timestamp, 1d) as timestamp'\n    usage_by_tenant_by_day = __query_cloudwatch_logs(logs, log_group_names, usage_by_tenant_by_day_query, start_date_time, end_date_time)\n\n    print(usage_by_tenant_by_day) \n\n    total_usage_by_day_query = 'filter @message like /Request completed/ \\\n        | fields CountLambdaInvocations.0 As LambdaInvocations, timestamp\\\n        | stats count (tenant_id) as CountLambdaInvocations by dateceil(@timestamp, 1d) as timestamp'\n    \n    total_usage_by_day = __query_cloudwatch_logs(logs,  log_group_names, \n    total_usage_by_day_query, start_date_time, end_date_time)\n\n    print(total_usage_by_day) \n    \n    total_invocations = 1 #to avoid divide by zero\n    for result in total_usage_by_day['results'][0]:\n        if 'LambdaInvocations' in result['field']:\n            total_invocations = Decimal(result['value'])\n        \n    \n    print (total_invocations)\n    \n    if (total_invocations>0):\n        total_invocations_by_tenant = 0\n        \n        for result in usage_by_tenant_by_day['results']:\n            for field in result:\n                if 'TenantId' in field['field']:\n                    tenant_id = field['value']\n                if 'LambdaInvocations' in field['field']:\n                    total_invocations_by_tenant = Decimal(field['value'])\n                \n            \n            #RCU is about 5 times cheaper\n            tenant_attribution_percentage= (total_invocations_by_tenant / total_invocations) \n            tenant_lambda_cost = tenant_attribution_percentage * total_lambda_cost\n            \n            try:\n                response = attribution_table.put_item(\n                    Item=\n                        {\n                            \"Date\": start_date_time,\n                            \"TenantId#ServiceName\": tenant_id+\"#\"+\"AWSLambda\",\n                            \"TenantId\": tenant_id, \n                            \"TotalInvocations\": total_invocations, \n                            \"TenantTotalInvocations\": total_invocations_by_tenant,\n                            \"TenantAttributionPercentage\": tenant_attribution_percentage,\n                            \"TenantServiceCost\": tenant_lambda_cost,\n                            \"TotalServiceCost\": total_lambda_cost\n                        }\n                )\n            except ClientError as e:\n                print(e.response['Error']['Message'])\n                raise Exception('Error adding a product', e)\n            else:\n                print(\"PutItem succeeded:\")\n            \n            tenant_id = 'unknown'\n            tenant_total_RCU = 0.0\n            tenant_total_WCU = 0.0\n\ndef __get_total_service_cost(servicename, start_date_time, end_date_time):\n\n    # We need to add more filters for day, month, year, resource ids etc. Below query is because we are just using a sample cur file\n    #Ignoting startTime and endTime filter for now since we have a static/sample cur file\n    \n    query = \"SELECT sum(line_item_blended_cost) AS cost FROM costexplorerdb.curoutput WHERE line_item_product_code='{0}'\".format(servicename) \n\n    # Execution\n    response = athena.start_query_execution(\n        QueryString=query,\n        QueryExecutionContext={\n            'Database': 'costexplorerdb'\n        },\n        ResultConfiguration={\n            'OutputLocation': \"s3://\" + ATHENA_S3_OUTPUT,\n        }\n    )\n\n    # get query execution id\n    query_execution_id = response['QueryExecutionId']\n    print(query_execution_id)\n\n    # get execution status\n    for i in range(1, 1 + RETRY_COUNT):\n\n        # get query execution\n        query_status = athena.get_query_execution(QueryExecutionId=query_execution_id)\n        print (query_status)\n        query_execution_status = query_status['QueryExecution']['Status']['State']\n\n        if query_execution_status == 'SUCCEEDED':\n            print(\"STATUS:\" + query_execution_status)\n            break\n\n        if query_execution_status == 'FAILED':\n            raise Exception(\"STATUS:\" + query_execution_status)\n\n        else:\n            print(\"STATUS:\" + query_execution_status)\n            time.sleep(i)\n    else:\n        athena.stop_query_execution(QueryExecutionId=query_execution_id)\n        raise Exception('TIME OVER')\n\n    # get query results\n    result = athena.get_query_results(QueryExecutionId=query_execution_id)\n    \n    print (result)\n    \n    \n    total_dynamo_db_cost = result['ResultSet']['Rows'][1]['Data'][0]['VarCharValue']\n    print(total_dynamo_db_cost)\n    \n    return Decimal(total_dynamo_db_cost)\n    \ndef __query_cloudwatch_logs(logs, log_group_names, query_string, start_time, end_time):\n    query = logs.start_query(logGroupNames=log_group_names,\n    startTime=start_time,\n    endTime=end_time,\n    queryString=query_string)\n\n    query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    while query_results['status']=='Running' or query_results['status']=='Scheduled':\n        time.sleep(5)\n        query_results = logs.get_query_results(queryId=query[\"queryId\"])\n\n    return query_results\n\ndef __is_log_group_exists(logs_client, log_group_name):\n    \n    logs_paginator = logs_client.get_paginator('describe_log_groups')\n    response_iterator = logs_paginator.paginate(logGroupNamePrefix=log_group_name)\n    for log_groups_list in response_iterator:\n        if not log_groups_list[\"logGroups\"]:\n            return False\n        else:\n            return True       \n\ndef __add_log_group_name(logs_client, log_group_name, log_group_names_list):\n\n    if __is_log_group_exists(logs_client, log_group_name):\n        log_group_names_list.append(log_group_name)\n\n\ndef __get_list_of_log_group_names():\n    log_group_names = []\n    log_group_prefix = '/aws/lambda/'\n    cloudformation_paginator = cloudformation.get_paginator('list_stack_resources')\n    response_iterator = cloudformation_paginator.paginate(StackName='stack-pooled')\n    for stack_resources in response_iterator:\n        for resource in stack_resources['StackResourceSummaries']:\n            if (resource[\"LogicalResourceId\"] == \"CreateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue    \n            if (resource[\"LogicalResourceId\"] == \"UpdateProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetProductsFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names)\n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteProductFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue         \n            if (resource[\"LogicalResourceId\"] == \"CreateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"UpdateOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"GetOrdersFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue\n            if (resource[\"LogicalResourceId\"] == \"DeleteOrderFunction\"):\n                __add_log_group_name(logs, ''.join([log_group_prefix,resource[\"PhysicalResourceId\"]]), \n                 log_group_names) \n                continue \n\n    return log_group_names        \n\n\ndef __get_start_date_time():\n    time_zone = datetime.now().astimezone().tzinfo\n    start_date_time = int(datetime.now(tz=time_zone).date().strftime('%s')) #current day epoch\n    return start_date_time\n\ndef __get_end_date_time():\n    time_zone = datetime.now().astimezone().tzinfo    \n    end_date_time =  int((datetime.now(tz=time_zone) + timedelta(days=1)).date().strftime('%s')) #next day epoch\n    return end_date_time\n\n\n"
  },
  {
    "path": "Solution/Lab7/deployment.sh",
    "content": "REGION=$(aws configure get region)\nsam build -t template.yaml --use-container\nsam deploy --config-file samconfig.toml --region=$REGION\n  \n\nCUR_BUCKET=$(aws cloudformation list-exports --query \"Exports[?Name=='CURBucketname'].Value\" --output text)\nAWSCURInitializerFunctionName=$(aws cloudformation list-exports --query \"Exports[?Name=='AWSCURInitializerFunctionName'].Value\" --output text)\n\naws s3 cp SampleCUR/ s3://$CUR_BUCKET/curoutput/year=2022/month=10/ --recursive\n\naws lambda invoke --function-name $AWSCURInitializerFunctionName lambdaoutput.json"
  },
  {
    "path": "Solution/Lab7/lambdaoutput.json",
    "content": "\"Crawler with name AWSCURCrawler-Multi-tenant has already started\""
  },
  {
    "path": "Solution/Lab7/samconfig.toml",
    "content": "version = 0.1\n[default]\n[default.deploy]\n[default.deploy.parameters]\nstack_name = \"serverless-saas-cost-per-tenant-lab7\"\ns3_bucket = \"aws-sam-cli-managed-default-samclisourcebucket-1p6m7gm2vwaaz\"\ns3_prefix = \"serverless-saas-lab7\"\nregion = \"us-west-2\"\nconfirm_changeset = false\ncapabilities = \"CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND\"\ncached=\"true\"\nparallel=\"true\""
  },
  {
    "path": "Solution/Lab7/template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Serverless SaaS - Cost by tenant\n\nGlobals:\n  Function:\n    Timeout: 29\n\nResources: \n  CURBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n        \n  AWSCURDatabase:\n    Type: 'AWS::Glue::Database'\n    Properties:\n      DatabaseInput:\n        Name: !Sub 'costexplorerdb'\n      CatalogId: !Ref AWS::AccountId\n\n         \n\n  AWSCURCrawlerComponentFunction:\n    Type: 'AWS::IAM::Role'\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - glue.amazonaws.com\n            Action:\n              - 'sts:AssumeRole'\n      Path: /\n      ManagedPolicyArns:\n        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole'\n      Policies:\n        - PolicyName: AWSCURCrawlerComponentFunction\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - 'logs:CreateLogGroup'\n                  - 'logs:CreateLogStream'\n                  - 'logs:PutLogEvents'\n                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'\n              - Effect: Allow\n                Action:\n                  - 'glue:UpdateDatabase'\n                  - 'glue:UpdatePartition'\n                  - 'glue:CreateTable'\n                  - 'glue:UpdateTable'\n                  - 'glue:ImportCatalogToGlue'\n                Resource: '*'\n              - Effect: Allow\n                Action:\n                  - 's3:GetObject'\n                  - 's3:PutObject'\n                Resource: !Sub '${CURBucket.Arn}*'\n        - PolicyName: AWSCURKMSDecryption\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - 'kms:Decrypt'\n                Resource: '*'\n       \n\n  AWSCURCrawlerLambdaExecutor:\n    Type: 'AWS::IAM::Role'\n    Properties:\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - 'sts:AssumeRole'\n      Path: /\n      Policies:\n        - PolicyName: AWSCURCrawlerLambdaExecutor\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - 'logs:CreateLogGroup'\n                  - 'logs:CreateLogStream'\n                  - 'logs:PutLogEvents'\n                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'\n              - Effect: Allow\n                Action:\n                  - 'glue:StartCrawler'\n                Resource: '*'\n       \n\n  AWSCURCrawler:\n    Type: 'AWS::Glue::Crawler'\n    DependsOn:\n      - AWSCURDatabase\n      - AWSCURCrawlerComponentFunction\n    Properties:\n      Name: AWSCURCrawler-Multi-tenant\n      Description: A recurring crawler that keeps your CUR table in Athena up-to-date.\n      Role: !GetAtt AWSCURCrawlerComponentFunction.Arn\n      DatabaseName: !Ref AWSCURDatabase\n      Targets:\n        S3Targets:\n          - Path: !Sub 's3://${CURBucket}/curoutput'\n            Exclusions:\n              - '**.json'\n              - '**.yml'\n              - '**.sql'\n              - '**.csv'\n              - '**.gz'\n              - '**.zip'\n      SchemaChangePolicy:\n        UpdateBehavior: UPDATE_IN_DATABASE\n        DeleteBehavior: DELETE_FROM_DATABASE\n       \n\n  AWSCURInitializer:\n    Type: 'AWS::Lambda::Function'\n    DependsOn: AWSCURCrawler\n    Properties:\n      Code:\n        ZipFile: >\n          const AWS = require('aws-sdk');\n          const response = require('./cfn-response');\n          exports.handler = function(event, context, callback) {\n            if (event.RequestType === 'Delete') {\n              response.send(event, context, response.SUCCESS);\n            } else {\n              const glue = new AWS.Glue();\n              glue.startCrawler({ Name: 'AWSCURCrawler-Multi-tenant' }, function(err, data) {\n                if (err) {\n                  const responseData = JSON.parse(this.httpResponse.body);\n                  if (responseData['__type'] == 'CrawlerRunningException') {\n                    callback(null, responseData.Message);\n                  } else {\n                    const responseString = JSON.stringify(responseData);\n                    if (event.ResponseURL) {\n                      response.send(event, context, response.FAILED,{ msg: responseString });\n                    } else {\n                      callback(responseString);\n                    }\n                  }\n                }\n                else {\n                  if (event.ResponseURL) {\n                    response.send(event, context, response.SUCCESS);\n                  } else {\n                    callback(null, response.SUCCESS);\n                  }\n                }\n              });\n            }\n          };\n      Handler: 'index.handler'\n      Timeout: 30\n      Runtime: nodejs16.x\n      ReservedConcurrentExecutions: 1\n      Role: !GetAtt AWSCURCrawlerLambdaExecutor.Arn\n\n\n  \n  TenantCostandUsageAttributionTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n        - AttributeName: Date\n          AttributeType: N\n        - AttributeName: TenantId#ServiceName\n          AttributeType: S\n      KeySchema:\n        - AttributeName: Date\n          KeyType: HASH\n        - AttributeName: TenantId#ServiceName\n          KeyType: RANGE\n      BillingMode: PAY_PER_REQUEST \n      TableName: TenantCostAndUsageAttribution\n  \n  QueryLogInsightsExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: product-function-execution-role-lab1\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - lambda.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns:\n        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n      Policies:\n        - PolicyName: query-log-insight-lab7\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:              \n              - Effect: Allow\n                Action:\n                  - logs:GetQueryResults\n                  - logs:StartQuery\n                  - logs:StopQuery\n                  - logs:FilterLogEvents\n                  - logs:DescribeLogGroups\n                  - cloudformation:ListStackResources\n                Resource:\n                  - \"*\"\n              - Effect: Allow\n                Action:\n                  - s3:*\n                Resource:\n                  - !Sub 'arn:aws:s3:::${CURBucket}*'\n              - Effect: Allow\n                Action:\n                  - dynamodb:*\n                Resource:\n                  - !GetAtt TenantCostandUsageAttributionTable.Arn\n              - Effect: Allow\n                Action:\n                  - Athena:*\n                Resource:\n                  - \"*\"\n              - Effect: Allow\n                Action:\n                  - glue:*\n                Resource:\n                  - \"*\"\n                  \n  GetDynamoDBUsageAndCostByTenant:\n    Type: AWS::Serverless::Function \n    DependsOn: QueryLogInsightsExecutionRole \n    Properties:\n      CodeUri: TenantUsageAndCost/\n      Handler: tenant_usage_and_cost.calculate_daily_dynamodb_attribution_by_tenant\n      Runtime: python3.9  \n      Role: !GetAtt QueryLogInsightsExecutionRole.Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT: !Ref CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateDynamoUsageAndCostByTenant\n            Schedule: rate(5 minutes)\n  \n  GetLambdaUsageAndCostByTenant:\n    Type: AWS::Serverless::Function \n    DependsOn: QueryLogInsightsExecutionRole \n    Properties:\n      CodeUri: TenantUsageAndCost/\n      Handler: tenant_usage_and_cost.calculate_daily_lambda_attribution_by_tenant\n      Runtime: python3.9  \n      Role: !GetAtt QueryLogInsightsExecutionRole.Arn\n      Environment:\n        Variables:\n          ATHENA_S3_OUTPUT: !Ref CURBucket\n      Events:\n        ScheduledEvent:\n          Type: Schedule\n          Properties:\n            Name: CalculateLambdaUsageAndCostByTenant\n            Schedule: rate(5 minutes)\n            \nOutputs:\n    CURBucketname:\n        Description: The name of S3 bucket name\n        Value: !Ref CURBucket\n        Export:\n          Name: \"CURBucketname\"       \n\n    AWSCURInitializerFunctionName:\n        Description: Function name of CUR initializer\n        Value: !Ref AWSCURInitializer\n        Export:\n          Name: \"AWSCURInitializerFunctionName\"     "
  },
  {
    "path": "THIRD-PARTY-LICENSES.txt",
    "content": "** CrHelper; version 2.0.6 --\nhttps://github.com/aws-cloudformation/custom-resource-helper\n\nApache License\n\nVersion 2.0, January 2004\n\nhttp://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND\nDISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction, and\n      distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by the\n      copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all other\n      entities that control, are controlled by, or are under common control\n      with that entity. For the purposes of this definition, \"control\" means\n      (i) the power, direct or indirect, to cause the direction or management\n      of such entity, whether by contract or otherwise, or (ii) ownership of\n      fifty percent (50%) or more of the outstanding shares, or (iii)\n      beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\n      permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation source,\n      and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but not limited\n      to compiled object code, generated documentation, and conversions to\n      other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or Object\n      form, made available under the License, as indicated by a copyright\n      notice that is included in or attached to the work (an example is\n      provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object form,\n      that is based on (or derived from) the Work and for which the editorial\n      revisions, annotations, elaborations, or other modifications represent,\n      as a whole, an original work of authorship. For the purposes of this\n      License, Derivative Works shall not include works that remain separable\n      from, or merely link (or bind by name) to the interfaces of, the Work and\n      Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including the original\n      version of the Work and any modifications or additions to that Work or\n      Derivative Works thereof, that is intentionally submitted to Licensor for\n      inclusion in the Work by the copyright owner or by an individual or Legal\n      Entity authorized to submit on behalf of the copyright owner. For the\n      purposes of this definition, \"submitted\" means any form of electronic,\n      verbal, or written communication sent to the Licensor or its\n      representatives, including but not limited to communication on electronic\n      mailing lists, source code control systems, and issue tracking systems\n      that are managed by, or on behalf of, the Licensor for the purpose of\n      discussing and improving the Work, but excluding communication that is\n      conspicuously marked or otherwise designated in writing by the copyright\n      owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity on\n      behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of this\n   License, each Contributor hereby grants to You a perpetual, worldwide,\n   non-exclusive, no-charge, royalty-free, irrevocable copyright license to\n   reproduce, prepare Derivative Works of, publicly display, publicly perform,\n   sublicense, and distribute the Work and such Derivative Works in Source or\n   Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of this\n   License, each Contributor hereby grants to You a perpetual, worldwide,\n   non-exclusive, no-charge, royalty-free, irrevocable (except as stated in\n   this section) patent license to make, have made, use, offer to sell, sell,\n   import, and otherwise transfer the Work, where such license applies only to\n   those patent claims licensable by such Contributor that are necessarily\n   infringed by their Contribution(s) alone or by combination of their\n   Contribution(s) with the Work to which such Contribution(s) was submitted.\n   If You institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work or a\n   Contribution incorporated within the Work constitutes direct or contributory\n   patent infringement, then any patent licenses granted to You under this\n   License for that Work shall terminate as of the date such litigation is\n   filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the Work or\n   Derivative Works thereof in any medium, with or without modifications, and\n   in Source or Object form, provided that You meet the following conditions:\n\n      (a) You must give any other recipients of the Work or Derivative Works a\n      copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices stating\n      that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works that You\n      distribute, all copyright, patent, trademark, and attribution notices\n      from the Source form of the Work, excluding those notices that do not\n      pertain to any part of the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n      distribution, then any Derivative Works that You distribute must include\n      a readable copy of the attribution notices contained within such NOTICE\n      file, excluding those notices that do not pertain to any part of the\n      Derivative Works, in at least one of the following places: within a\n      NOTICE text file distributed as part of the Derivative Works; within the\n      Source form or documentation, if provided along with the Derivative\n      Works; or, within a display generated by the Derivative Works, if and\n      wherever such third-party notices normally appear. The contents of the\n      NOTICE file are for informational purposes only and do not modify the\n      License. You may add Your own attribution notices within Derivative Works\n      that You distribute, alongside or as an addendum to the NOTICE text from\n      the Work, provided that such additional attribution notices cannot be\n      construed as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and may\n      provide additional or different license terms and conditions for use,\n      reproduction, or distribution of Your modifications, or for any such\n      Derivative Works as a whole, provided Your use, reproduction, and\n      distribution of the Work otherwise complies with the conditions stated in\n      this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise, any\n   Contribution intentionally submitted for inclusion in the Work by You to the\n   Licensor shall be under the terms and conditions of this License, without\n   any additional terms or conditions. Notwithstanding the above, nothing\n   herein shall supersede or modify the terms of any separate license agreement\n   you may have executed with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor, except\n   as required for reasonable and customary use in describing the origin of the\n   Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or agreed to in\n   writing, Licensor provides the Work (and each Contributor provides its\n   Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n   KIND, either express or implied, including, without limitation, any\n   warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or\n   FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining\n   the appropriateness of using or redistributing the Work and assume any risks\n   associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory, whether\n   in tort (including negligence), contract, or otherwise, unless required by\n   applicable law (such as deliberate and grossly negligent acts) or agreed to\n   in writing, shall any Contributor be liable to You for damages, including\n   any direct, indirect, special, incidental, or consequential damages of any\n   character arising as a result of this License or out of the use or inability\n   to use the Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all other\n   commercial damages or losses), even if such Contributor has been advised of\n   the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing the Work\n   or Derivative Works thereof, You may choose to offer, and charge a fee for,\n   acceptance of support, warranty, indemnity, or other liability obligations\n   and/or rights consistent with this License. However, in accepting such\n   obligations, You may act only on Your own behalf and on Your sole\n   responsibility, not on behalf of any other Contributor, and only if You\n   agree to indemnify, defend, and hold each Contributor harmless for any\n   liability incurred by, or claims asserted against, such Contributor by\n   reason of your accepting any such warranty or additional liability. END OF\n   TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification\nwithin third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\n\nyou may not use this file except in compliance with the License.\n\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\n\ndistributed under the License is distributed on an \"AS IS\" BASIS,\n\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\nSee the License for the specific language governing permissions and\n\nlimitations under the License.\n\n* For CrHelper see also this required NOTICE:\n    Custom Resource Helper Copyright 2019 Amazon.com, Inc. or its affiliates.\n    All Rights Reserved.\n    This library is licensed under the Apache 2.0 License.\n    Decorator implementation inspired by\n    https://github.com/ryansb/cfn-wrapper-python\n    Log implementation inspired by\n    https://gitlab.com/hadrien/aws_lambda_logging\n\n------\n\n** jsonpickle; version 1.4.1 -- https://github.com/jsonpickle/jsonpickle\nCopyright (C) 2008 John Paulett (john -at- paulett.org)\nCopyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n 1. Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in\n    the documentation and/or other materials provided with the\n    distribution.\n 3. The name of the author may not be used to endorse or promote\n    products derived from this software without specific prior\n    written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS\nOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE\nGOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER\nIN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\nOTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN\nIF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nCopyright (C) 2008 John Paulett (john -at- paulett.org)\nCopyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com)\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n 1. Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in\n    the documentation and/or other materials provided with the\n    distribution.\n 3. The name of the author may not be used to endorse or promote\n    products derived from this software without specific prior\n    written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS\nOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE\nGOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER\nIN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\nOTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN\nIF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n------\n\n** aws-requests-auth; version 0.4.3 --\nhttps://github.com/davidmuller/aws-requests-auth\nCopyright (c) David Muller.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification,\nare permitted provided that the following conditions are met:\n\n    1. Redistributions of source code must retain the above copyright notice,\n       this list of conditions and the following disclaimer.\n\n    2. Redistributions in binary form must reproduce the above copyright\n       notice, this list of conditions and the following disclaimer in the\n       documentation and/or other materials provided with the distribution.\n\n    3. The names of its contributors may not be used to endorse or promote\n       products derived from this software without specific prior written\n       permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nCopyright (c) David Muller.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification,\nare permitted provided that the following conditions are met:\n\n    1. Redistributions of source code must retain the above copyright notice,\n       this list of conditions and the following disclaimer.\n\n    2. Redistributions in binary form must reproduce the above copyright\n       notice, this list of conditions and the following disclaimer in the\n       documentation and/or other materials provided with the distribution.\n\n    3. The names of its contributors may not be used to endorse or promote\n       products derived from this software without specific prior written\n       permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n------\n\n** python-jose; version 3.2.0 -- https://github.com/mpdavis/python-jose\nCopyright (c) 2015 Michael Davis\n** python-simplejson; version 3.17.2 --\nhttps://github.com/simplejson/simplejson\nCopyright (c) 2006 Bob Ippolito\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "event-engine-assets/initialize-module-sam-template.yaml",
    "content": "AWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Create Cloud9 IDE, create state machine to intall pre-req on cloud9, API Gateway cloudwatch Role\n\nParameters:\n  # Optional parameters passed by the Event Engine to the stack.\n  # EEEventId:\n  #   Description: \"Unique ID of this Event\"\n  #   Type: String\n  # EETeamId:\n  #   Description: \"Unique ID of this Team\"\n  #   Type: String\n  # EEModuleId:\n  #   Description: \"Unique ID of this module\"\n  #   Type: String\n  # EEModuleVersion:\n  #   Description: \"Version of this module\"\n  #   Type: String\n  EEAssetsBucket:\n    Description: \"Region-specific assets S3 bucket name (e.g. ee-assets-prod-us-east-1)\"\n    Type: String\n    Default: \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\n  EEAssetsKeyPrefix:\n    Description: \"S3 key prefix where this modules assets are stored. (e.g. modules/my_module/v1/)\"\n    Type: String\n    Default: \"serverless-saas/\"\n  # EEMasterAccountId:\n  #   Description: \"AWS Account Id of the Master account\"\n  #   Type: String\n  # EETeamRoleArn:\n  #   Description: \"ARN of the Team Role\"\n  #   Type: String\n  # EEKeyPair:\n  #   Description: \"Name of the EC2 KeyPair generated for the Team\"\n  #   Type: AWS::EC2::KeyPair::KeyName\n  # Your own parameters for the stack. NOTE: All these parameters need to have a default value.\n  EBSVolumeSize:\n    Description: \"Size of EBS Volume (in GB)\"\n    Type: Number\n    Default: 50\n  UserDataScript:\n    Description: \"File name for user-data script\"\n    Type: String\n    Default: \"pre-requisites-event-engine.sh\"\n  EC2InstanceType:\n    Default: t3.large\n    Description: EC2 instance type on which IDE runs\n    Type: String\n  AutoHibernateTimeout:\n    Default: 120\n    Description: How many minutes idle before shutting down the IDE\n    Type: Number\n  OwnerRoleName:\n    Default: \"ServerlessSaaS/shaanubh-Isengard\"\n    Description: Use this if you are accessing your AWS Account using a cross account role (as is the case with event engine)\n    Type: String    \n\nConditions:\n  AddUserData: !Not [!Equals [ !Ref UserDataScript, \"NONE\" ]]\n  UseRole: !Not [!Equals [ !Ref OwnerRoleName, \"\"]]\n\nResources:  \n  EC2SSMExecutionRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      RoleName: ec2-ssm-role\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - ec2.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore \n      Policies:\n      - PolicyName: ec2-ssm-policy\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:        \n          - Effect: Allow\n            Action:\n              - \"ec2:ModifyVolume\"\n              - \"ec2:DescribeInstances\"\n              - \"ec2:DescribeVolumesModifications\"\n              - \"logs:*\"              \n            Resource: \"*\"        \n\n  WaitForEC2ToInitialize:\n    Type: AWS::Serverless::Function\n    Properties:\n      InlineCode: |\n        import boto3\n        import time\n        ec2_client = boto3.client('ec2')\n        \n        def lambda_handler(event, context):\n          instance_id = event['resources'][0].split(\"/\")[1]\n          instance_statuses = ec2_client.describe_instance_status(InstanceIds=[instance_id])\n          try:\n            state = instance_statuses['InstanceStatuses'][0]['InstanceState']['Name']\n            instance_status = instance_statuses['InstanceStatuses'][0]['InstanceStatus']['Details'][0]['Status']\n            system_status = instance_statuses['InstanceStatuses'][0]['SystemStatus']['Details'][0]['Status']\n          except Exception as _:\n            state = 'UNKNOWN'\n\n          while (instance_status != 'passed' or system_status != 'passed' or state != 'running'):\n            print(\"waiting for system to be ready\")\n            time.sleep(30)\n            instance_statuses = ec2_client.describe_instance_status(InstanceIds=[instance_id])\n            print (instance_statuses)\n            try:\n              state = instance_statuses['InstanceStatuses'][0]['InstanceState']['Name']\n              instance_status = instance_statuses['InstanceStatuses'][0]['InstanceStatus']['Details'][0]['Status']\n              system_status = instance_statuses['InstanceStatuses'][0]['SystemStatus']['Details'][0]['Status']\n            except Exception as e:\n                print(e)\n                state = 'UNKNOWN'\n\n          environment_info = {\n            \"instance_id\": instance_id            \n          }\n          return environment_info\n        \n      Handler: index.lambda_handler\n      Runtime: python3.9\n      Timeout: 900\n      Policies:\n      - Statement:        \n        - Sid: EC2\n          Effect: Allow\n          Action:\n            - \"ec2:DescribeInstanceStatus\"            \n          Resource: \"*\"\n          \n  AttachSSMRoleToEC2:\n    Type: AWS::Serverless::Function\n    Properties:\n      InlineCode: |\n        import boto3\n        import json\n        import os\n        from time import sleep\n        iam_client= boto3.client('iam')\n        ec2_client = boto3.client('ec2')\n        \n        def lambda_handler(event, context):\n            print(event)\n            instance_id = event[\"instance_id\"]\n            instance_profile_name = 'cloud9-' + instance_id\n\n            response = ec2_client.describe_iam_instance_profile_associations(Filters=[{'Name': 'instance-id','Values': [instance_id]},{'Name': 'state','Values': ['associated']}])\n            if (len(response['IamInstanceProfileAssociations']) < 1):\n              try:\n                  create_instance_profile_response = iam_client.create_instance_profile(InstanceProfileName=instance_profile_name)\n                  print(create_instance_profile_response)\n                  sleep(30)\n              except iam_client.exceptions.EntityAlreadyExistsException as _:\n                  pass\n              \n              try:\n                  response = iam_client.add_role_to_instance_profile(\n                      InstanceProfileName=instance_profile_name,\n                      RoleName='ec2-ssm-role'\n                  )\n                  print(response)\n\n                  response = ec2_client.associate_iam_instance_profile(\n                      IamInstanceProfile={'Name': instance_profile_name},\n                      InstanceId=instance_id\n                  )\n                  print(response)\n              except iam_client.exceptions.LimitExceededException as e:\n                  print(e)\n              \n              response = ec2_client.describe_iam_instance_profile_associations(Filters=[{'Name': 'instance-id','Values': [instance_id]},{'Name': 'state','Values': ['associated']}])\n              print(response)\n              \n              while len(response['IamInstanceProfileAssociations']) < 1:\n                  print(\"waiting for association to finish\")\n                  sleep(30)\n                  response = ec2_client.describe_iam_instance_profile_associations(Filters=[{'Name': 'instance-id','Values': [instance_id]},{'Name': 'state','Values': ['associated']}])\n\n            environment_info = {\n                \"instance_id\": instance_id,                                \n                \"instance_profile_name\": instance_profile_name\n            }\n            return environment_info\n        \n      Handler: index.lambda_handler\n      Runtime: python3.9\n      Timeout: 900\n      Policies:\n      - Statement:\n        - Sid: ResizePolicy\n          Effect: Allow\n          Action:\n            - \"ec2:DescribeIamInstanceProfileAssociations\"\n            - \"ec2:AssociateIamInstanceProfile\"\n            - \"iam:CreateInstanceProfile\"\n            - \"iam:AddRoleToInstanceProfile\"       \n            - \"iam:PassRole\"     \n          Resource: \"*\"\n\n  SendSSMCommandtoEC2:\n    Type: AWS::Serverless::Function\n    Properties:\n      InlineCode: |\n        import boto3\n        import json\n        import os\n        from io import BytesIO\n        s3_resource = boto3.resource('s3')\n        ssm_client = boto3.client('ssm')\n        import time\n\n        def get_preamble():\n            return f\"\"\"\n        REGION=$(curl http://169.254.169.254/latest/meta-data/placement/region)\n        aws configure set profile.default.region $REGION\n\n        SIZE={int(os.environ.get('VolumeSize', '25'))}\n\n        # Get the ID of the environment host Amazon EC2 instance.\n        INSTANCEID=$(curl http://169.254.169.254/latest/meta-data//instance-id)\n\n        # Get the ID of the Amazon EBS volume associated with the instance.\n        VOLUMEID=$(aws ec2 describe-instances \\\n          --instance-id $INSTANCEID \\\n          --query \"Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId\" \\\n          --output text)\n\n        # Resize the EBS volume.\n        aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE\n\n        # Wait for the resize to finish.\n        while [ \\\n          \"$(aws ec2 describe-volumes-modifications \\\n            --volume-id $VOLUMEID \\\n            --filters Name=modification-state,Values=\"optimizing\",\"completed\" \\\n            --query \"length(VolumesModifications)\"\\\n            --output text)\" != \"1\" ]; do\n          sleep 1\n        done\n        \n        #Check if we're on an NVMe filesystem\n        if [[ -e \"/dev/xvda\" && $(readlink -f /dev/xvda) = \"/dev/xvda\" ]]\n        then\n          # Rewrite the partition table so that the partition takes up all the space that it can.\n          sudo growpart /dev/xvda 1\n\n          # Expand the size of the file system.\n          # Check if we're on AL2\n          STR=$(cat /etc/os-release)\n          SUB='VERSION_ID=\"2\"'\n          if [[ \"$STR\" == *\"$SUB\"* ]]\n          then\n            sudo xfs_growfs -d /\n          else\n            sudo resize2fs /dev/xvda1\n          fi\n\n        else\n          # Rewrite the partition table so that the partition takes up all the space that it can.\n          sudo growpart /dev/nvme0n1 1\n\n          # Expand the size of the file system.\n          # Check if we're on AL2\n          STR=$(cat /etc/os-release)\n          SUB='VERSION_ID=\"2\"'\n          if [[ \"$STR\" == *\"$SUB\"* ]]\n          then\n            sudo xfs_growfs -d /\n          else\n            sudo resize2fs /dev/nvme0n1p1\n          fi\n        fi\n        \"\"\"\n\n        def ssm_ready(ssm_client, instance_id):\n          try:\n            response = ssm_client.describe_instance_information(Filters=[{'Key': 'InstanceIds', 'Values': [instance_id]}])\n            return len(response['InstanceInformationList'])>=1\n          except ssm_client.exceptions.InvalidInstanceId:\n            return False\n\n        def lambda_handler(event, context):\n          print(event)\n          instance_id = event[\"instance_id\"]\n          \n          while not ssm_ready(ssm_client, instance_id):\n            print(\"SSM not ready yet\")\n            time.sleep(30)\n            \n          try:\n            print(os.environ[\"S3Bucket\"])\n            print(os.environ[\"S3Object\"])\n            bucket = s3_resource.Bucket(os.environ[\"S3Bucket\"])\n            obj = bucket.Object(os.environ[\"S3Object\"])\n            output = BytesIO()\n            obj.download_fileobj(output)\n            commands = get_preamble() + '\\n' + output.getvalue().decode('utf-8') + '\\n'\n          except Exception as e:\n            print(e)\n            commands = get_preamble()\n\n          print (instance_id)\n          \n          send_command_response = ssm_client.send_command(\n            InstanceIds=[instance_id],\n            DocumentName='AWS-RunShellScript',\n            Parameters={'commands': commands.split('\\n')},\n            CloudWatchOutputConfig={\n                'CloudWatchLogGroupName': f'ssm-output-{instance_id}',\n                'CloudWatchOutputEnabled': True\n            }\n          )\n\n          environment_info = {\n            \"instance_id\": instance_id,\n            \"command_id\": send_command_response['Command']['CommandId']\n          }\n          return environment_info\n\n      Handler: index.lambda_handler\n      Runtime: python3.9\n      Timeout: 900\n      Environment:\n        Variables:\n          VolumeSize: !Ref EBSVolumeSize\n          S3Bucket: !If\n            - AddUserData\n            - !Ref EEAssetsBucket\n            - !Ref \"AWS::NoValue\"\n          S3Object: !If\n            - AddUserData\n            - !Sub \"${EEAssetsKeyPrefix}${UserDataScript}\"\n            - !Ref \"AWS::NoValue\"\n      Policies:\n      - Statement:\n        - Sid: ResizePolicy\n          Effect: Allow\n          Action:\n            - \"s3:*\"\n            - \"ssm:*\"\n            - \"cloudwatch:*\"\n          Resource: \"*\"\n\n  WaitForSSMCommandToComplete:\n    Type: AWS::Serverless::Function\n    Properties:\n      InlineCode: |\n        import boto3\n        import time\n\n        def lambda_handler(event, context):\n          command_id = event[\"command_id\"]\n          instance_id = event[\"instance_id\"]\n          ssm_client = boto3.client('ssm')\n          response = ssm_client.get_command_invocation(CommandId=command_id, InstanceId=instance_id)\n          while (response['Status'] in ['Pending', 'InProgress', 'Delayed']):\n            time.sleep(30)\n            print(\"Command in Progress\")\n            response = ssm_client.get_command_invocation(CommandId=command_id, InstanceId=instance_id)\n          \n      Handler: index.lambda_handler\n      Runtime: python3.9\n      Timeout: 900\n      Policies:\n      - Statement:\n        - Sid: ResizePolicy\n          Effect: Allow\n          Action:\n            - \"ssm:GetCommandInvocation\"\n          Resource: \"*\"\n            \n  BootStrapC9StateMachine:\n    Type: AWS::Serverless::StateMachine\n    Properties:\n      Definition:\n        StartAt: Environment Health Check\n        States:\n          Environment Health Check:\n            Type: Task\n            Resource: ${WaitForEC2ToInitializeArn}\n            Retry:\n            - ErrorEquals:\n              - States.TaskFailed\n              IntervalSeconds: 30\n              MaxAttempts: 2\n              BackoffRate: 1.5\n            Next: Attach SSM Role To EC2\n          Attach SSM Role To EC2:\n            Type: Task\n            Resource: ${AttachSSMRoleToEC2Arn}\n            Retry:\n            - ErrorEquals:\n              - States.TaskFailed\n              IntervalSeconds: 30\n              MaxAttempts: 2\n              BackoffRate: 1.5\n            Next: Send Command\n          Send Command:\n            Type: Task\n            Resource: ${SendSSMCommandtoEC2Arn}\n            Retry:\n            - ErrorEquals:\n              - States.TaskFailed\n              IntervalSeconds: 30\n              MaxAttempts: 2\n              BackoffRate: 1.5\n            Next: Wait To Stabilize\n          Wait To Stabilize:\n            Type: Task\n            Resource: ${WaitForSSMCommandToCompleteArn}\n            Retry:\n            - ErrorEquals:\n              - States.TaskFailed\n              IntervalSeconds: 30\n              MaxAttempts: 2\n              BackoffRate: 1.5\n            End: true\n      DefinitionSubstitutions:\n        WaitForEC2ToInitializeArn: !GetAtt WaitForEC2ToInitialize.Arn\n        AttachSSMRoleToEC2Arn: !GetAtt AttachSSMRoleToEC2.Arn\n        SendSSMCommandtoEC2Arn: !GetAtt SendSSMCommandtoEC2.Arn\n        WaitForSSMCommandToCompleteArn: !GetAtt WaitForSSMCommandToComplete.Arn                \n      Policies:\n      - LambdaInvokePolicy:\n          FunctionName: !Ref WaitForEC2ToInitialize\n      - LambdaInvokePolicy:\n          FunctionName: !Ref AttachSSMRoleToEC2\n      - LambdaInvokePolicy:\n          FunctionName: !Ref SendSSMCommandtoEC2\n      - LambdaInvokePolicy:\n          FunctionName: !Ref WaitForSSMCommandToComplete\n  \n  EventBridgeRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - events.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      Policies:      \n        - PolicyName: serverless-saas-eventbridge-stepfunction-policy\n          PolicyDocument:\n            Version: 2012-10-17\n            Statement:\n              - Effect: Allow\n                Action:\n                  - states:StartExecution                      \n                Resource:\n                  - !GetAtt BootStrapC9StateMachine.Arn              \n\n  NewCloud9Event:\n    Type: AWS::Events::Rule\n    Properties:\n      EventPattern:\n        source:\n        - aws.ec2\n        detail-type:\n        - EC2 Instance State-change Notification\n        detail:\n          state:\n          - running      \n      Targets:\n        - Arn : !GetAtt BootStrapC9StateMachine.Arn\n          Id: \"BootStrapC9StateMachineTarget\"\n          RoleArn: !GetAtt EventBridgeRole.Arn\n\n  Cloud9IDE:\n    Type: AWS::Cloud9::EnvironmentEC2\n    DependsOn: NewCloud9Event\n    Properties:\n      Repositories:\n      - RepositoryUrl: https://github.com/aws-samples/aws-serverless-saas-workshop.git\n        PathComponent: /aws-serverless-saas-workshop\n      Description: Cloud9 IDE\n      Tags:\n        - Key: stack-name\n          Value: ServerlessSaaS\n      AutomaticStopTimeMinutes:\n        Ref: AutoHibernateTimeout      \n      ImageId: amazonlinux-2-x86_64\n      InstanceType:\n        Ref: EC2InstanceType\n      Name: Serverless-SaaS\n      OwnerArn: !If [UseRole, !Join [ \"\", [!Sub \"arn:aws:sts::${AWS::AccountId}:assumed-role/\", !Ref OwnerRoleName]] , !Ref \"AWS::NoValue\"]\n  \nOutputs:\n  BootStrapC9StateMachineArn:\n    Description: \"State machine ARN\"\n    Value: !Ref BootStrapC9StateMachine"
  },
  {
    "path": "event-engine-assets/lab1-module-sam-template.yaml",
    "content": "AWSTemplateFormatVersion: '2010-09-09'\nTransform: AWS::Serverless-2016-10-31\nDescription: 'Lab1 - Basic Serverless Application'\n\nParameters:\n  EEAssetsBucket:\n    Description: \"Region-specific assets S3 bucket name (e.g. ee-assets-prod-us-east-1)\"\n    Type: String\n    Default: \"aws-sam-cli-managed-default-samclisourcebucket-8tf6bmi4rdcx\"\n  EEAssetsKeyPrefix:\n    Description: \"S3 key prefix where this modules assets are stored. (e.g. modules/my_module/v1/)\"\n    Type: String\n    Default: \"serverless-saas/\"\n  StageName:\n    Type: String\n    Default: prod\n    Description: Stage Name for the api\n\nGlobals:\n  Function:\n    Timeout: 29\n    Layers:\n    - Fn::Sub: arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14\n    Environment:\n      Variables:\n        LOG_LEVEL: DEBUG\n  \nResources:\n  ApiGatewayCloudWatchPublishRole:\n    Type: AWS::IAM::Role     \n    Properties:\n      Path: '/'\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n          - Effect: Allow\n            Principal:\n              Service:\n                - apigateway.amazonaws.com\n            Action:\n              - sts:AssumeRole\n      ManagedPolicyArns: \n        - arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs        \n  \n  AttachCloudWatchRoleToApiGateway:\n    Type: AWS::ApiGateway::Account\n    Properties: \n      CloudWatchRoleArn: !GetAtt ApiGatewayCloudWatchPublishRole.Arn\n\n  ServerlessSaaSLayers:\n    Type: AWS::Serverless::LayerVersion\n    Properties:\n      LayerName: serverless-saas-workshoplab1\n      Description: Utilities for project\n      ContentUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}1b6d1796b5948e393749602691c77e44\n      CompatibleRuntimes:\n      - python3.9\n      LicenseInfo: MIT\n      RetentionPolicy: Retain\n    Metadata:\n      BuildMethod: python3.9\n  ProductTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n      - AttributeName: productId\n        AttributeType: S\n      KeySchema:\n      - AttributeName: productId\n        KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: Product-Lab1\n  OrderTable:\n    Type: AWS::DynamoDB::Table\n    Properties:\n      AttributeDefinitions:\n      - AttributeName: orderId\n        AttributeType: S\n      KeySchema:\n      - AttributeName: orderId\n        KeyType: HASH\n      BillingMode: PAY_PER_REQUEST\n      TableName: Order-Lab1\n  ProductFunctionExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: product-function-execution-role-lab1\n      Path: /\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - lambda.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      ManagedPolicyArns:\n      - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n      - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n      - PolicyName: product-function-policy-lab1\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - dynamodb:GetItem\n            - dynamodb:UpdateItem\n            - dynamodb:PutItem\n            - dynamodb:DeleteItem\n            - dynamodb:Query\n            - dynamodb:Scan\n            Resource:\n            - Fn::GetAtt:\n              - ProductTable\n              - Arn\n  GetProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}3f9284a8b1fb9547d59e56cc8560b94c\n      Handler: product_service.get_product\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - ProductFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: ProductService\n          PRODUCT_TABLE_NAME:\n            Ref: ProductTable\n  GetProductsFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}3f9284a8b1fb9547d59e56cc8560b94c\n      Handler: product_service.get_products\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - ProductFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: ProductService\n          PRODUCT_TABLE_NAME:\n            Ref: ProductTable\n  CreateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}3f9284a8b1fb9547d59e56cc8560b94c\n      Handler: product_service.create_product\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - ProductFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: ProductService\n          PRODUCT_TABLE_NAME:\n            Ref: ProductTable\n  UpdateProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}3f9284a8b1fb9547d59e56cc8560b94c\n      Handler: product_service.update_product\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - ProductFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: ProductService\n          PRODUCT_TABLE_NAME:\n            Ref: ProductTable\n  DeleteProductFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: ProductFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}3f9284a8b1fb9547d59e56cc8560b94c\n      Handler: product_service.delete_product\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - ProductFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: ProductService\n          PRODUCT_TABLE_NAME:\n            Ref: ProductTable\n  OrderFunctionExecutionRole:\n    Type: AWS::IAM::Role\n    Properties:\n      RoleName: order-function-execution-role-lab1\n      Path: /\n      AssumeRolePolicyDocument:\n        Version: 2012-10-17\n        Statement:\n        - Effect: Allow\n          Principal:\n            Service:\n            - lambda.amazonaws.com\n          Action:\n          - sts:AssumeRole\n      ManagedPolicyArns:\n      - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy\n      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\n      - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess\n      Policies:\n      - PolicyName: order-function-policy-lab1\n        PolicyDocument:\n          Version: 2012-10-17\n          Statement:\n          - Effect: Allow\n            Action:\n            - dynamodb:GetItem\n            - dynamodb:UpdateItem\n            - dynamodb:PutItem\n            - dynamodb:DeleteItem\n            - dynamodb:Query\n            - dynamodb:Scan\n            Resource:\n            - Fn::GetAtt:\n              - OrderTable\n              - Arn\n  GetOrdersFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}ac28465dbac64f71e4739d551119c420\n      Handler: order_service.get_orders\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - OrderFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: OrderService\n          ORDER_TABLE_NAME:\n            Ref: OrderTable\n  GetOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}ac28465dbac64f71e4739d551119c420\n      Handler: order_service.get_order\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - OrderFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: OrderService\n          ORDER_TABLE_NAME:\n            Ref: OrderTable\n  CreateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}ac28465dbac64f71e4739d551119c420\n      Handler: order_service.create_order\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - OrderFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: OrderService\n          ORDER_TABLE_NAME:\n            Ref: OrderTable\n  UpdateOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}ac28465dbac64f71e4739d551119c420\n      Handler: order_service.update_order\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - OrderFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: OrderService\n          ORDER_TABLE_NAME:\n            Ref: OrderTable\n  DeleteOrderFunction:\n    Type: AWS::Serverless::Function\n    DependsOn: OrderFunctionExecutionRole\n    Properties:\n      CodeUri: \n        Bucket: !Ref EEAssetsBucket\n        Key: !Sub ${EEAssetsKeyPrefix}ac28465dbac64f71e4739d551119c420\n      Handler: order_service.delete_order\n      Runtime: python3.9\n      Tracing: Active\n      Role:\n        Fn::GetAtt:\n        - OrderFunctionExecutionRole\n        - Arn\n      Layers:\n      - Ref: ServerlessSaaSLayers\n      Environment:\n        Variables:\n          POWERTOOLS_SERVICE_NAME: OrderService\n          ORDER_TABLE_NAME:\n            Ref: OrderTable\n  ApiGatewayAccessLogs:\n    Type: AWS::Logs::LogGroup\n    Properties:\n      LogGroupName: /aws/api-gateway/access-logs-serverless-saas-workshop-lab1-api\n      RetentionInDays: 30\n  ApiGatewayApi:\n    Type: AWS::Serverless::Api\n    Properties:\n      MethodSettings:\n      - DataTraceEnabled: true\n        LoggingLevel: INFO\n        MetricsEnabled: true\n        ResourcePath: /*\n        HttpMethod: '*'\n      AccessLogSetting:\n        DestinationArn:\n          Fn::GetAtt:\n          - ApiGatewayAccessLogs\n          - Arn\n        Format: '{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\",\n          \"caller\":\"$context.identity.caller\", \"user\":\"$context.identity.user\",\"requestTime\":\"$context.requestTime\",\n          \"httpMethod\":\"$context.httpMethod\",\"resourcePath\":\"$context.resourcePath\",\n          \"status\":\"$context.status\",\"protocol\":\"$context.protocol\", \"responseLength\":\"$context.responseLength\"\n          }'\n      TracingEnabled: true\n      DefinitionBody:\n        openapi: 3.0.1\n        info:\n          title: serverless-saas-workshop-lab1\n        basePath:\n          Fn::Join:\n          - ''\n          - - /\n            - Ref: StageName\n        schemes:\n        - https\n        paths:\n          /order/{id}:\n            get:\n              summary: Returns a order\n              description: Return a order by a order id.\n              produces:\n              - application/json\n              parameters:\n              - name: id\n                in: path\n                required: true\n                type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - GetOrderFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:\n              produces:\n              - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - UpdateOrderFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:\n              summary: Deletes a order\n              description: Deletes a order by a order id.\n              produces:\n              - application/json\n              parameters:\n              - name: id\n                in: path\n                required: true\n                type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - DeleteOrderFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n              - application/json\n              produces:\n              - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: '#/definitions/Empty'\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'''\n                      method.response.header.Access-Control-Allow-Headers: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'''\n                      method.response.header.Access-Control-Allow-Origin: '''*'''\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /orders:\n            get:\n              summary: Returns all orders\n              description: Returns all orders.\n              produces:\n              - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - GetOrdersFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n              - application/json\n              produces:\n              - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: '#/definitions/Empty'\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'''\n                      method.response.header.Access-Control-Allow-Headers: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'''\n                      method.response.header.Access-Control-Allow-Origin: '''*'''\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /order:\n            post:\n              produces:\n              - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - CreateOrderFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n              - application/json\n              produces:\n              - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: '#/definitions/Empty'\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'''\n                      method.response.header.Access-Control-Allow-Headers: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'''\n                      method.response.header.Access-Control-Allow-Origin: '''*'''\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /product/{id}:\n            get:\n              summary: Returns a product\n              description: Return a product by a product id.\n              produces:\n              - application/json\n              parameters:\n              - name: id\n                in: path\n                required: true\n                type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - GetProductFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            put:\n              produces:\n              - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - UpdateProductFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            delete:\n              summary: Deletes a product\n              description: Deletes a product by a product id.\n              produces:\n              - application/json\n              parameters:\n              - name: id\n                in: path\n                required: true\n                type: string\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - DeleteProductFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n              - application/json\n              produces:\n              - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: '#/definitions/Empty'\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'''\n                      method.response.header.Access-Control-Allow-Headers: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'''\n                      method.response.header.Access-Control-Allow-Origin: '''*'''\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /products:\n            get:\n              summary: Returns all products\n              description: Returns all products.\n              produces:\n              - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - GetProductsFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n              - application/json\n              produces:\n              - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: '#/definitions/Empty'\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'''\n                      method.response.header.Access-Control-Allow-Headers: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'''\n                      method.response.header.Access-Control-Allow-Origin: '''*'''\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n          /product:\n            post:\n              produces:\n              - application/json\n              responses: {}\n              x-amazon-apigateway-integration:\n                uri:\n                  Fn::Join:\n                  - ''\n                  - - Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/\n                    - Fn::GetAtt:\n                      - CreateProductFunction\n                      - Arn\n                    - /invocations\n                httpMethod: POST\n                type: aws_proxy\n            options:\n              consumes:\n              - application/json\n              produces:\n              - application/json\n              responses:\n                '200':\n                  description: 200 response\n                  schema:\n                    $ref: '#/definitions/Empty'\n                  headers:\n                    Access-Control-Allow-Origin:\n                      type: string\n                    Access-Control-Allow-Methods:\n                      type: string\n                    Access-Control-Allow-Headers:\n                      type: string\n              x-amazon-apigateway-integration:\n                responses:\n                  default:\n                    statusCode: 200\n                    responseParameters:\n                      method.response.header.Access-Control-Allow-Methods: '''DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'''\n                      method.response.header.Access-Control-Allow-Headers: '''Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'''\n                      method.response.header.Access-Control-Allow-Origin: '''*'''\n                passthroughBehavior: when_no_match\n                requestTemplates:\n                  application/json: '{\"statusCode\": 200}'\n                type: mock\n      StageName:\n        Ref: StageName\n  GetProductsLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - GetProductsFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  GetProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - GetProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  CreateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - CreateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  UpdateProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - UpdateProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  DeleteProductLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - DeleteProductFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  GetOrdersLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - GetOrdersFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  GetOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - GetOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  CreateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - CreateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  UpdateOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - UpdateOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  DeleteOrderLambdaApiGatewayExecutionPermission:\n    Type: AWS::Lambda::Permission\n    Properties:\n      Action: lambda:InvokeFunction\n      FunctionName:\n        Fn::GetAtt:\n        - DeleteOrderFunction\n        - Arn\n      Principal: apigateway.amazonaws.com\n      SourceArn:\n        Fn::Join:\n        - ''\n        - - 'arn:aws:execute-api:'\n          - Ref: AWS::Region\n          - ':'\n          - Ref: AWS::AccountId\n          - ':'\n          - Ref: ApiGatewayApi\n          - /*/*/*\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: Origin Access Identity for CloudFront Distribution\n  AppBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy: Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration:\n        BlockPublicAcls: true\n        BlockPublicPolicy: true\n        IgnorePublicAcls: true\n        RestrictPublicBuckets: true\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Properties:\n      Bucket:\n        Ref: AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: s3:GetObject\n          Effect: Allow\n          Resource:\n            Fn::Sub: arn:aws:s3:::${AppBucket}/*\n          Principal:\n            CanonicalUser:\n              Fn::GetAtt:\n              - CloudFrontOriginAccessIdentity\n              - S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        - ErrorCode: 403\n          ResponseCode: 200\n          ResponsePagePath: /index.html\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: /index.html\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - DELETE\n          - GET\n          - HEAD\n          - OPTIONS\n          - PATCH\n          - POST\n          - PUT\n          Compress: true\n          DefaultTTL: 3600\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400\n          MinTTL: 60\n          TargetOriginId: lab1-tenantapp-s3origin\n          ViewerProtocolPolicy: allow-all\n        DefaultRootObject: index.html\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName:\n            Fn::GetAtt:\n            - AppBucket\n            - RegionalDomainName\n          Id: lab1-tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity:\n              Fn::Join:\n              - ''\n              - - origin-access-identity/cloudfront/\n                - Ref: CloudFrontOriginAccessIdentity\n        PriceClass: PriceClass_All\nOutputs:\n  APIGatewayURL:\n    Description: API Gateway endpoint URL for API\n    Value:\n      Fn::Join:\n      - ''\n      - - Fn::Sub: https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/\n        - Ref: StageName\n        - /\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value:\n      Fn::GetAtt:\n      - ApplicationSite\n      - DomainName\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value:\n      Ref: AppBucket\n"
  },
  {
    "path": "event-engine-assets/pre-requisites-event-engine.sh",
    "content": "#!/bin/bash -x\n. /home/ec2-user/.nvm/nvm.sh\n\n#Install python3.8\nsudo yum install -y amazon-linux-extras\nsudo amazon-linux-extras enable python3.8\nsudo yum install -y python3.8\nsudo alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1\nsudo alternatives --set python3 /usr/bin/python3.8\n\n# Uninstall aws cli v1 and Install aws cli version-2.3.0\nsudo pip2 uninstall awscli -y\n\necho \"Installing aws cli version-2.3.0\"\ncurl \"https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.3.0.zip\" -o \"awscliv2.zip\"\nunzip awscliv2.zip\nsudo ./aws/install\nrm awscliv2.zip\nrm -rf aws \n\n# Install sam cli version 1.64.0\necho \"Installing sam cli version 1.64.0\"\nwget https://github.com/aws/aws-sam-cli/releases/download/v1.64.0/aws-sam-cli-linux-x86_64.zip\nunzip aws-sam-cli-linux-x86_64.zip -d sam-installation\nsudo ./sam-installation/install\nif [ $? -ne 0 ]; then\n\techo \"Sam cli is already present, so deleting existing version\"\n\tsudo rm /usr/local/bin/sam\n\tsudo rm -rf /usr/local/aws-sam-cli\n\techo \"Now installing sam cli version 1.64.0\"\n\tsudo ./sam-installation/install    \nfi\nrm aws-sam-cli-linux-x86_64.zip\nrm -rf sam-installation\n\n# Install git-remote-codecommit version 1.15.1\necho \"Installing git-remote-codecommit version 1.15.1\"\ncurl -O https://bootstrap.pypa.io/get-pip.py\npython3 get-pip.py --user\nrm get-pip.py\n\npython3 -m pip install git-remote-codecommit==1.15.1\n\n# Install node v14.18.1\necho \"Installing node v14.18.1\"\nnvm deactivate\nnvm uninstall node\nnvm install v14.18.1\nnvm use v14.18.1\nnvm alias default v14.18.1\n\nnpm set unsafe-perm true\n\n# Install cdk cli version ^2.40.0\necho \"Installing cdk cli version^2.40.0\"\nnpm uninstall -g aws-cdk\nnpm install -g aws-cdk@\"^2.40.0\"\n\n#Install jq version 1.5\nsudo yum -y install jq-1.5\n\n#Install pylint version 2.11.1\npython3 -m pip install pylint==2.11.1\n\npython3 -m pip install boto3"
  },
  {
    "path": "event-engine-assets/userinterface-module-sam-template.yaml",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n# SPDX-License-Identifier: MIT-0\n\nAWSTemplateFormatVersion: \"2010-09-09\"\nTransform: AWS::Serverless-2016-10-31\nDescription: >\n  Template to deploy cloudfront and s3 bucket for UI code. \n  This template will be used to pre-provision cloudfront and \n  s3 buckets for UI code using event engine module during \n  AWS hosted events. So that we don't need to provision them \n  in individual labs and it improves individual labs execution time.\nResources:\n  CloudFrontOriginAccessIdentity:\n    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity\n    Properties:\n      CloudFrontOriginAccessIdentityConfig:\n        Comment: \"Origin Access Identity for CloudFront Distributions\"\n  AdminAppBucket:  \n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AdminAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Properties:\n      Bucket: !Ref AdminAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AdminAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  AdminAppSite:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        #Aliases: \n         # - !Sub 'admin.${CustomDomainName}'\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          CachedMethods:\n          - GET\n          - HEAD\n          - OPTIONS\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: adminapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt AdminAppBucket.RegionalDomainName\n          Id: adminapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'  \n  LandingAppBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  \n  LandingAppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Properties:\n      Bucket: !Ref LandingAppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${LandingAppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  LandingApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: landingapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'LandingAppBucket.RegionalDomainName'\n          Id: landingapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All' \n  AppBucket:\n    Type: AWS::S3::Bucket\n    DeletionPolicy : Retain\n    Properties:\n      BucketEncryption:\n        ServerSideEncryptionConfiguration:\n          - ServerSideEncryptionByDefault:\n              SSEAlgorithm: 'AES256'\n      PublicAccessBlockConfiguration: \n        BlockPublicAcls: True\n        BlockPublicPolicy: True\n        IgnorePublicAcls: True\n        RestrictPublicBuckets: True\n  AppSiteReadPolicy:\n    Type: AWS::S3::BucketPolicy\n    Properties:\n      Bucket: !Ref AppBucket\n      PolicyDocument:\n        Statement:\n        - Action: 's3:GetObject'\n          Effect: Allow\n          Resource: !Sub 'arn:aws:s3:::${AppBucket}/*'\n          Principal:\n            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId\n  ApplicationSite:\n    Type: AWS::CloudFront::Distribution\n    Properties:\n      DistributionConfig:\n        CustomErrorResponses:\n        # Needed to support angular routing\n        - ErrorCode: 403 \n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        - ErrorCode: 404\n          ResponseCode: 200\n          ResponsePagePath: '/index.html'\n        DefaultCacheBehavior:\n          AllowedMethods:\n            - DELETE\n            - GET\n            - HEAD\n            - OPTIONS\n            - PATCH\n            - POST\n            - PUT\n          Compress: true\n          DefaultTTL: 3600 # in seconds\n          ForwardedValues:\n            Cookies:\n              Forward: none\n            QueryString: false\n          MaxTTL: 86400 # in seconds\n          MinTTL: 60 # in seconds\n          TargetOriginId: tenantapp-s3origin\n          ViewerProtocolPolicy: 'allow-all'\n        DefaultRootObject: 'index.html'\n        Enabled: true\n        HttpVersion: http2\n        Origins:\n        - DomainName: !GetAtt 'AppBucket.RegionalDomainName'\n          Id: tenantapp-s3origin\n          S3OriginConfig:\n            OriginAccessIdentity: !Join [\"\", [\"origin-access-identity/cloudfront/\", !Ref CloudFrontOriginAccessIdentity]] \n        PriceClass: 'PriceClass_All'          \nOutputs:\n  AdminBucket:\n    Description: The name of the bucket for uploading the Admin Management site to\n    Value: !Ref AdminAppBucket\n    Export:\n      Name: \"Serverless-SaaS-AdminSiteBucket\"\n  AdminAppSite:\n    Description: The name of the CloudFront url for Admin Management site\n    Value: !GetAtt AdminAppSite.DomainName\n    Export:\n      Name: \"Serverless-SaaS-AdminAppSite\"\n  LandingAppBucket:\n    Description: The name of the bucket for uploading the Landing site to\n    Value: !Ref LandingAppBucket\n    Export:\n      Name: \"Serverless-SaaS-LandingApplicationSiteBucket\"\n  LandingApplicationSite:\n    Description: The name of the CloudFront url for Landing site\n    Value: !GetAtt LandingApplicationSite.DomainName\n    Export:\n      Name: \"Serverless-SaaS-LandingApplicationSite\"\n  AppBucket:\n    Description: The name of the bucket for uploading the Tenant Management site to\n    Value: !Ref AppBucket\n    Export:\n      Name: \"Serverless-SaaS-ApplicationSiteBucket\"\n  ApplicationSite:\n    Description: The name of the CloudFront url for Tenant Management site\n    Value: !GetAtt ApplicationSite.DomainName     \n    Export:\n      Name: \"Serverless-SaaS-ApplicationSite\""
  },
  {
    "path": "scripts/cleanup.sh",
    "content": "#!/bin/bash\n##\n## This script aims to clean up resources created for the\n## SaaS Serverless Workshop. This script is based on the guidance\n## provided here:\n## https://catalog.us-east-1.prod.workshops.aws/workshops/b0c6ad36-0a4b-45d8-856b-8a64f0ac76bb/en-US/cleanup\n##\n## Note that this script can also be used to clean up resources for the\n## Serverless SaaS Reference Solution as outlined here:\n## https://github.com/aws-samples/aws-saas-factory-ref-solution-serverless-saas#steps-to-clean-up\n##\n##\n\n# helper function\ndelete_stack_after_confirming() {\n    if [[ -z \"${1}\" ]]; then\n        echo \"$(date) stack name missing...\"\n        return\n    fi\n\n    stack=$(aws cloudformation describe-stacks --stack-name \"$1\")\n    if [[ -z \"${stack}\" ]]; then\n        echo \"$(date) stack ${1} does not exist...\"\n        return\n    fi\n\n    if [[ -z \"${skip_flag}\" ]]; then\n        read -p \"Delete stack with name $1 [Y/n] \" -n 1 -r\n    fi\n\n    if [[ $REPLY =~ ^[n]$ ]]; then\n        echo \"$(date) NOT deleting stack $1.\"\n    else\n        echo \"$(date) deleting stack $1...\"\n        aws cloudformation delete-stack --stack-name \"$1\"\n\n        echo \"$(date) waiting for stack delete operation to complete...\"\n        aws cloudformation wait stack-delete-complete --stack-name \"$1\"\n    fi\n}\n\n# helper function\ndelete_codecommit_repo_after_confirming() {\n    REPO_NAME=\"$1\"\n    repo=$(aws codecommit get-repository --repository-name \"$REPO_NAME\")\n    if [[ -n \"${repo}\" ]]; then\n\n        if [[ -z \"${skip_flag}\" ]]; then\n            read -p \"Delete codecommit repo with name \\\"$REPO_NAME\\\" [Y/n] \" -n 1 -r\n        fi\n\n        if [[ $REPLY =~ ^[n]$ ]]; then\n            echo \"$(date) NOT deleting $REPO_NAME.\"\n        else\n            echo \"$(date) deleting codecommit repo \\\"$REPO_NAME\\\"...\"\n            aws codecommit delete-repository --repository-name \"$REPO_NAME\"\n        fi\n    else\n        echo \"$(date) repo \\\"$REPO_NAME\\\" does not exist...\"\n    fi\n}\n\nskip_flag=''\nwhile getopts 's' flag; do\n    case \"${flag}\" in\n    s) skip_flag='true' ;;\n    *) error \"Unexpected option ${flag}!\" && exit 1 ;;\n    esac\ndone\n\necho \"$(date) Checking for prerequisites...\"\njq --version || {\n    echo \"jq missing! Please install before using this script.\"\n    exit 1\n}\naws --version || {\n    echo \"ÅWS cli missing! Please install before using this script.\"\n    exit 1\n}\necho \"$(date) Done checking for prerequisites.\"\n\necho \"$(date) Cleaning up resources...\"\nif [[ -n \"${skip_flag}\" ]]; then\n    echo \"skip_flag enabled. Script will not pause for confirmation before deleting resources!\"\nelse\n    echo \"skip_flag disabled. Script will pause for confirmation before deleting resources.\"\nfi\n\ndelete_stack_after_confirming \"serverless-saas-workshop-lab1\"\ndelete_stack_after_confirming \"stack-pooled\"\ndelete_stack_after_confirming \"serverless-saas-cost-per-tenant-lab7\"\n\necho \"$(date) cleaning up platinum tenants...\"\nnext_token=\"\"\nSTACK_STATUS_FILTER=\"CREATE_COMPLETE ROLLBACK_COMPLETE UPDATE_COMPLETE UPDATE_ROLLBACK_COMPLETE IMPORT_COMPLETE IMPORT_ROLLBACK_COMPLETE\"\nwhile true; do\n    if [[ \"${next_token}\" == \"\" ]]; then\n        echo \"$(date) making api call to search for platinum tenants...\"\n        # shellcheck disable=SC2086\n        # ignore shellcheck error for adding a quote as that causes the api call to fail\n        response=$(aws cloudformation list-stacks --stack-status-filter $STACK_STATUS_FILTER)\n    else\n        echo \"$(date) making api call to search for platinum tenants...\"\n        # shellcheck disable=SC2086\n        # ignore shellcheck error for adding a quote as that causes the api call to fail\n        response=$(aws cloudformation list-stacks --stack-status-filter $STACK_STATUS_FILTER --starting-token \"$next_token\")\n    fi\n\n    tenant_stacks=$(echo \"$response\" | jq -r '.StackSummaries[].StackName | select(. | test(\"^stack-*\"))')\n    for i in $tenant_stacks; do\n        delete_stack_after_confirming \"$i\"\n    done\n\n    next_token=$(echo \"$response\" | jq '.NextToken')\n    if [[ \"${next_token}\" == \"null\" ]]; then\n        echo \"$(date) no more platinum tenants left.\"\n        # no more results left. Exit loop...\n        break\n    fi\ndone\n\ndelete_stack_after_confirming \"serverless-saas\"\ndelete_stack_after_confirming \"serverless-saas-pipeline\"\n\n# delete_codecommit_repo_after_confirming \"aws-saas-factory-ref-serverless-saas\"\ndelete_codecommit_repo_after_confirming \"aws-serverless-saas-workshop\"\n\necho \"$(date) cleaning up buckets...\"\nfor i in $(aws s3 ls | awk '{print $3}' | grep -E \"^serverless-saas-*|^sam-bootstrap-*\"); do\n\n    if [[ -z \"${skip_flag}\" ]]; then\n        read -p \"Delete bucket with name s3://${i} [Y/n] \" -n 1 -r\n    fi\n\n    if [[ $REPLY =~ ^[n]$ ]]; then\n        echo \"$(date) NOT deleting bucket s3://${i}.\"\n    else\n        echo \"$(date) emptying out s3 bucket with name s3://${i}...\"\n        aws s3 rm --recursive \"s3://${i}\"\n\n        echo \"$(date) deleting s3 bucket with name s3://${i}...\"\n        aws s3 rb \"s3://${i}\"\n    fi\ndone\n\necho \"$(date) cleaning up log groups...\"\nnext_token=\"\"\nwhile true; do\n    if [[ \"${next_token}\" == \"\" ]]; then\n        response=$(aws logs describe-log-groups)\n    else\n        response=$(aws logs describe-log-groups --starting-token \"$next_token\")\n    fi\n\n    log_groups=$(echo \"$response\" | jq -r '.logGroups[].logGroupName | select(. | test(\"^/aws/lambda/stack-*|^/aws/lambda/serverless-saas-*\"))')\n    for i in $log_groups; do\n        if [[ -z \"${skip_flag}\" ]]; then\n            read -p \"Delete log group with name $i [Y/n] \" -n 1 -r\n        fi\n\n        if [[ $REPLY =~ ^[n]$ ]]; then\n            echo \"$(date) NOT deleting log group $i.\"\n        else\n            echo \"$(date) deleting log group with name $i...\"\n            aws logs delete-log-group --log-group-name \"$i\"\n        fi\n    done\n\n    next_token=$(echo \"$response\" | jq '.NextToken')\n    if [[ \"${next_token}\" == \"null\" ]]; then\n        # no more results left. Exit loop...\n        break\n    fi\ndone\n\necho \"$(date) cleaning up user pools...\"\nnext_token=\"\"\nwhile true; do\n    if [[ \"${next_token}\" == \"\" ]]; then\n        response=$(aws cognito-idp list-user-pools --max-results 1)\n    else\n        response=$(aws cognito-idp list-user-pools --max-results 1 --starting-token \"$next_token\")\n    fi\n\n    pool_ids=$(echo \"$response\" | jq -r '.UserPools[] | select(.Name | test(\"^.*-ServerlessSaaSUserPool$\")) |.Id')\n    for i in $pool_ids; do\n        if [[ -z \"${skip_flag}\" ]]; then\n            read -p \"Delete user pool with name $i [Y/n] \" -n 1 -r\n        fi\n\n        if [[ $REPLY =~ ^[n]$ ]]; then\n            echo \"$(date) NOT deleting user pool $i.\"\n        else\n            echo \"$(date) deleting user pool with name $i...\"\n            echo \"getting pool domain...\"\n            pool_domain=$(aws cognito-idp describe-user-pool --user-pool-id \"$i\" | jq -r '.UserPool.Domain')\n\n            echo \"deleting pool domain $pool_domain...\"\n            aws cognito-idp delete-user-pool-domain \\\n                --user-pool-id \"$i\" \\\n                --domain \"$pool_domain\"\n\n            echo \"deleting pool $i...\"\n            aws cognito-idp delete-user-pool --user-pool-id \"$i\"\n        fi\n    done\n\n    next_token=$(echo \"$response\" | jq '.NextToken')\n    if [[ \"${next_token}\" == \"null\" ]]; then\n        # no more results left. Exit loop...\n        break\n    fi\ndone\n\necho \"$(date) Done cleaning up resources!\"\n"
  },
  {
    "path": "scripts/create_tenants.sh",
    "content": "#!/bin/bash\n##\n## This script is to help create different kinds of tenants quickly.\n##\n## To use:\n## bash create_tenants.sh myemail mydomain.com\n##\n\nEMAIL_ALIAS=\"$1\"  # ex. test\nEMAIL_DOMAIN=\"$2\" # ex. test.com\n\necho \"$(date) finding saas admin API GW url...\"\nnext_token=\"\"\nwhile true; do\n    if [[ \"${next_token}\" == \"\" ]]; then\n        echo \"$(date) making api call to search for saas admin API GWs...\"\n        response=$(aws apigateway get-rest-apis)\n    else\n        echo \"$(date) making api call to search for saas admin API GWs...\"\n        response=$(aws apigateway get-rest-apis --starting-token \"$next_token\")\n    fi\n\n    api_gw_id=$(echo \"$response\" | jq -r '.items[] | select (.name | match(\"serverless-saas-admin-api\")) | .id')\n    if [[ \"${api_gw_id}\" != \"\" ]]; then\n        echo \"$(date) saas admin API rest id found!\"\n        # no need to look any further...\n        break\n    fi\n\n    next_token=$(echo \"$response\" | jq '.NextToken')\n    if [[ \"${next_token}\" == \"null\" ]]; then\n        echo \"$(date) no more API GWs left!\"\n        # no more results left. Exit loop...\n        break\n    fi\ndone\n\nSAAS_ADMIN_URL_STAGE=\"prod\"\nCURRENT_REGION=$(aws configure get region || echo \"$AWS_DEFAULT_REGION\")\nSAAS_ADMIN_URL=\"https://${api_gw_id}.execute-api.${CURRENT_REGION}.amazonaws.com/${SAAS_ADMIN_URL_STAGE}\" # ex. https://m6slpkzugb.execute-api.us-west-2.amazonaws.com\n\necho \"EMAIL_ALIAS=${EMAIL_ALIAS}\"\necho \"EMAIL_DOMAIN=${EMAIL_DOMAIN}\"\necho \"SAAS_ADMIN_URL=${SAAS_ADMIN_URL}\"\necho \"REGION=${CURRENT_REGION}\"\n\nread -rp \"press any key to confirm above parameters and continue...\"\n\necho \"$(date) Creating a Standard tenant...\"\ncurl --location --request POST \"${SAAS_ADMIN_URL}/registration\" \\\n    --header 'Content-Type: application/json' \\\n    --data-raw \"{\n    \\\"tenantName\\\": \\\"tenantstandardB\\\",\n    \\\"tenantAddress\\\": \\\"123 street\\\",\n    \\\"tenantEmail\\\": \\\"${EMAIL_ALIAS}+tenantstandardB@${EMAIL_DOMAIN}\\\",\n    \\\"tenantPhone\\\": \\\"1234567890\\\",\n    \\\"tenantTier\\\": \\\"Standard\\\"\n}\"\necho \"$(date) Done creating a Standard tenant!\"\n\necho \"$(date) Creating a Platinum tenant...\"\ncurl --location --request POST \"${SAAS_ADMIN_URL}/registration\" \\\n    --header 'Content-Type: application/json' \\\n    --data-raw \"{\n    \\\"tenantName\\\": \\\"tenantplatinumB\\\",\n    \\\"tenantAddress\\\": \\\"123 street\\\",\n    \\\"tenantEmail\\\": \\\"${EMAIL_ALIAS}+tenantplatinumB@${EMAIL_DOMAIN}\\\",\n    \\\"tenantPhone\\\": \\\"1234567890\\\",\n    \\\"tenantTier\\\": \\\"Platinum\\\"\n}\"\necho \"$(date) Done creating a Platinum tenant!\"\n\necho \"$(date) Creating a Premium tenant...\"\ncurl --location --request POST \"${SAAS_ADMIN_URL}/registration\" \\\n    --header 'Content-Type: application/json' \\\n    --data-raw \"{\n    \\\"tenantName\\\": \\\"tenantpremiumB\\\",\n    \\\"tenantAddress\\\": \\\"123 street\\\",\n    \\\"tenantEmail\\\": \\\"${EMAIL_ALIAS}+tenantpremiumB@${EMAIL_DOMAIN}\\\",\n    \\\"tenantPhone\\\": \\\"1234567890\\\",\n    \\\"tenantTier\\\": \\\"Premium\\\"\n}\"\necho \"$(date) Done creating a Premium tenant!\"\n"
  },
  {
    "path": "scripts/lab2_updates.py",
    "content": "from replace_function import replace_in_file\n\nlab2_tenant_mgmt_original_str = \"\"\"#TODO: Implement the below method\ndef get_tenant(event, context):\n    pass\n\"\"\"\n\nlab2_tenant_mgmt_update_str = \"\"\"def get_tenant(event, context):\n    tenant_id = event['pathParameters']['tenantid']\n    logger.info(\"Request received to get tenant details\")\n\n    tenant_details = table_tenant_details.get_item(\n        Key={\n            'tenantId': tenant_id,\n        },\n        AttributesToGet=[\n            'tenantName',\n            'tenantAddress',\n            'tenantEmail',\n            'tenantPhone'\n        ]\n    )\n    item = tenant_details['Item']\n    tenant_info = TenantInfo(item['tenantName'], item['tenantAddress'],item['tenantEmail'], item['tenantPhone'])\n    logger.info(tenant_info)\n\n    logger.info(\"Request completed to get tenant details\")\n    return utils.create_success_response(tenant_info.__dict__)\n\"\"\"\n\nreplace_in_file(lab2_tenant_mgmt_original_str, lab2_tenant_mgmt_update_str, \"../Lab2/server/TenantManagementService/tenant-management.py\")\n\nlab2_user_mgmt_original_str = \"\"\"#TODO: Implement the below method\ndef create_user(event, context):\n    pass\n\"\"\"\n\nlab2_user_mgmt_update_str = \"\"\"def create_user(event, context):\n    user_details = json.loads(event['body'])\n\n    logger.info(\"Request received to create new user\")\n    logger.info(event)\n\n    tenant_id = user_details['tenantId']\n\n    response = client.admin_create_user(\n        Username=user_details['userName'],\n        UserPoolId=user_pool_id,\n        ForceAliasCreation=True,\n        UserAttributes=[\n            {\n                'Name': 'email',\n                'Value': user_details['userEmail']\n            },\n            {\n                'Name': 'custom:userRole',\n                'Value': user_details['userRole']\n            },\n            {\n                'Name': 'custom:tenantId',\n                'Value': tenant_id\n            }\n        ]\n    )\n\n    logger.info(response)\n    user_mgmt = UserManagement()\n    user_mgmt.add_user_to_group(user_pool_id, user_details['userName'], tenant_id)\n    response_mapping = user_mgmt.create_user_tenant_mapping(user_details['userName'], tenant_id)\n\n    logger.info(\"Request completed to create new user \")\n    return utils.create_success_response(\"New user created\")\n\"\"\"\n\nreplace_in_file(lab2_user_mgmt_original_str, lab2_user_mgmt_update_str, \"../Lab2/server/TenantManagementService/user-management.py\")\n\nlab2_registration_svc_original_str = \"\"\"#TODO: Implement this method\ndef register_tenant(event, context):\n    pass\n\"\"\"\n\nlab2_registration_svc_update_str = \"\"\"def register_tenant(event, context):\n    try:\n        tenant_id = uuid.uuid1().hex\n        tenant_details = json.loads(event['body'])\n\n        tenant_details['tenantId'] = tenant_id\n\n        logger.info(tenant_details)\n\n        stage_name = event['requestContext']['stage']\n        host = event['headers']['Host']\n        auth = utils.get_auth(host, region)\n        headers = utils.get_headers(event)\n        create_user_response = __create_tenant_admin_user(tenant_details, headers, auth, host, stage_name)\n\n        logger.info (create_user_response)\n        tenant_details['tenantAdminUserName'] = create_user_response['message']['tenantAdminUserName']\n\n        create_tenant_response = __create_tenant(tenant_details, headers, auth, host, stage_name)\n        logger.info (create_tenant_response)\n\n    except Exception as e:\n        logger.error('Error registering a new tenant')\n        raise Exception('Error registering a new tenant', e)\n    else:\n        return utils.create_success_response(\"You have been registered in our system\")\n\"\"\"\n\nreplace_in_file(lab2_registration_svc_original_str, lab2_registration_svc_update_str, \"../Lab2/server/TenantManagementService/tenant-registration.py\")\n"
  },
  {
    "path": "scripts/lab3_updates.py",
    "content": "from replace_function import replace_in_file\n\nlab3_tenant_auth_original_str = \"\"\"    # TODO: Add tenant context to authResponse\"\"\"\n\nlab3_tenant_auth_update_str = \"\"\"    context = {\n        'userName': user_name,\n        'tenantId': tenant_id\n    }\n\n    authResponse['context'] = context\n\"\"\"\n\nreplace_in_file(lab3_tenant_auth_original_str, lab3_tenant_auth_update_str, \"../Lab3/server/Resources/tenant_authorizer.py\")\n\nlab3_shared_svc_original_str = \"\"\"    #TODO: Add policy so that only tenant and SaaS admins can add/modify tenant information\"\"\"\n\nlab3_shared_svc_update_str = \"\"\"    if (auth_manager.isTenantAdmin(user_role) or auth_manager.isSystemAdmin(user_role)):\n        policy.allowAllMethods()\n        if (auth_manager.isTenantAdmin(user_role)):\n            policy.denyMethod(HttpVerb.POST, \"tenant-activation\")\n            policy.denyMethod(HttpVerb.GET, \"tenants\")\n    else:\n        #if not tenant admin or system admin then only allow to get info and update info\n        policy.allowMethod(HttpVerb.GET, \"user/*\")\n        policy.allowMethod(HttpVerb.PUT, \"user/*\")\n\"\"\"\n\nreplace_in_file(lab3_shared_svc_original_str, lab3_shared_svc_update_str, \"../Lab3/server/Resources/shared_service_authorizer.py\")\n\nlab3_layer_original_str = \"\"\"#TODO: Implement the below method\ndef record_metric(event, metric_name, metric_unit, metric_value):\n    pass\n\"\"\"\n\nlab3_layer_update_str = \"\"\"def record_metric(event, metric_name, metric_unit, metric_value):\n    \\\"\\\"\\\"Record the metric in Cloudwatch using EMF format\n\n    Args:\n        event ([type]): [description]\n        metric_name ([type]): [description]\n        metric_unit ([type]): [description]\n        metric_value ([type]): [description]\n    \\\"\\\"\\\"\n    metrics.add_dimension(name=\"tenant_id\", value=event['requestContext']['authorizer']['tenantId'])\n    metrics.add_metric(name=metric_name, unit=metric_unit, value=metric_value)\n    metrics_object = metrics.serialize_metric_set()\n    metrics.clear_metrics()\n    print(json.dumps(metrics_object))\n\n\"\"\"\n\nreplace_in_file(lab3_layer_original_str, lab3_layer_update_str, \"../Lab3/server/layers/metrics_manager.py\")\n\nlab3_product_original_str = \"\"\"#TODO: Implement this method\ndef create_product(event, payload):\n    pass\n\"\"\"\n\nlab3_product_update_str = \"\"\"def create_product(event, payload):\n    tenantId = event['requestContext']['authorizer']['tenantId']\n\n    suffix = random.randrange(suffix_start, suffix_end)\n    shardId = tenantId+\"-\"+str(suffix)\n\n    product = Product(shardId, str(uuid.uuid4()), payload.sku,payload.name, payload.price, payload.category)\n\n    try:\n        response = table.put_item(\n            Item=\n                {\n                    'shardId': shardId,\n                    'productId': product.productId,\n                    'sku': product.sku,\n                    'name': product.name,\n                    'price': product.price,\n                    'category': product.category\n                }\n        )\n    except ClientError as e:\n        logger.error(e.response['Error']['Message'])\n        raise Exception('Error adding a product', e)\n    else:\n        logger.info(\"PutItem succeeded:\")\n        return product\n\"\"\"\nreplace_in_file(lab3_product_original_str, lab3_product_update_str, \"../Lab3/server/ProductService/product_service_dal.py\")"
  },
  {
    "path": "scripts/lab4_updates.py",
    "content": "from replace_function import replace_in_file\n\nlab4_tenant_auth_original_str = \"\"\"    #TODO : Add code for Fine-Grained-Access-Control\"\"\"\n\nlab4_tenant_auth_update_str = \"\"\"    iam_policy = auth_manager.getPolicyForUser(user_role, utils.Service_Identifier.BUSINESS_SERVICES.value, tenant_id, region, aws_account_id)\n    logger.info(iam_policy)\n\n    role_arn = \"arn:aws:iam::{}:role/authorizer-access-role\".format(aws_account_id)\n\n    assumed_role = sts_client.assume_role(\n        RoleArn=role_arn,\n        RoleSessionName=\"tenant-aware-session\",\n        Policy=iam_policy,\n    )\n    credentials = assumed_role[\"Credentials\"]\n    #pass sts credentials to lambda\n    context = {\n        'accesskey': credentials['AccessKeyId'], # $context.authorizer.key -> value\n        'secretkey' : credentials['SecretAccessKey'],\n        'sessiontoken' : credentials[\"SessionToken\"],\n        'userName': user_name,\n        'tenantId': tenant_id,\n        'userPoolId': userpool_id,\n        'userRole': user_role\n    }\n\n    authResponse['context'] = context\n\"\"\"\n\nreplace_in_file(lab4_tenant_auth_original_str, lab4_tenant_auth_update_str, \"../Lab4/server/Resources/tenant_authorizer.py\")\n\nlab4_product_original_str = \"\"\"    #TODO: Implement this method\"\"\"\n\nlab4_product_update_str = \"\"\"    accesskey = event['requestContext']['authorizer']['accesskey']\n    secretkey = event['requestContext']['authorizer']['secretkey']\n    sessiontoken = event['requestContext']['authorizer']['sessiontoken']\n    dynamodb = boto3.resource('dynamodb',\n                aws_access_key_id=accesskey,\n                aws_secret_access_key=secretkey,\n                aws_session_token=sessiontoken\n                )\n\n    return dynamodb.Table(table_name)\n\"\"\"\n\nreplace_in_file(lab4_product_original_str, lab4_product_update_str, \"../Lab4/server/ProductService/product_service_dal.py\")\nreplace_in_file(lab4_product_original_str, lab4_product_update_str, \"../Lab4/server/OrderService/order_service_dal.py\")\n"
  },
  {
    "path": "scripts/lab5_updates.py",
    "content": "from replace_function import replace_in_file\n\nlab5_user_mgmt_original_str = \"\"\"        #TODO: add code to provision new user pool\n        pass\n\"\"\"\n\nlab5_user_mgmt_update_str = \"\"\"        user_pool_response = user_mgmt.create_user_pool(tenant_id)\n        user_pool_id = user_pool_response['UserPool']['Id']\n        logger.info (user_pool_id)\n\n        app_client_response = user_mgmt.create_user_pool_client(user_pool_id)\n        logger.info(app_client_response)\n        app_client_id = app_client_response['UserPoolClient']['ClientId']\n        user_pool_domain_response = user_mgmt.create_user_pool_domain(user_pool_id, tenant_id)\n\n        logger.info (\"New Tenant Created\")\n\"\"\"\n\nreplace_in_file(lab5_user_mgmt_original_str, lab5_user_mgmt_update_str, \"../Lab5/server/TenantManagementService/user-management.py\")\n\nlab5_tenant_prov_original_str = \"\"\"        #TODO: Add missing code to kick off the pipeline\n        pass\n\"\"\"\n\nlab5_tenant_prov_update_str = \"\"\"        response_ddb = table_tenant_stack_mapping.put_item(\n            Item={\n                    'tenantId': tenant_details['tenantId'],\n                    'stackName': stack_name.format(tenant_details['tenantId']),\n                    'applyLatestRelease': True,\n                    'codeCommitId': ''\n                }\n            )\n\n        logger.info(response_ddb)\n\n        response_codepipeline = codepipeline.start_pipeline_execution(\n            name='serverless-saas-pipeline'\n        )\n\n        logger.info(response_ddb)\n\"\"\"\nreplace_in_file(lab5_tenant_prov_original_str, lab5_tenant_prov_update_str, \"../Lab5/server/TenantManagementService/tenant-provisioning.py\")\n"
  },
  {
    "path": "scripts/lab6_updates.py",
    "content": "from replace_function import replace_in_file\n\nlab6_tenant_reg_original_str = \"\"\"        #TODO: Pass relevant apikey to tenant_details object based upon tenant tier\n        if (tenant_details['tenantTier'].upper() == utils.TenantTier.PLATINUM.value.upper()):\n            tenant_details['dedicatedTenancy'] = 'true'\n\"\"\"\n\nlab6_tenant_reg_update_str = \"\"\"        if (tenant_details['tenantTier'].upper() == utils.TenantTier.PLATINUM.value.upper()):\n            tenant_details['dedicatedTenancy'] = 'true'\n            api_key = platinum_tier_api_key\n        elif (tenant_details['tenantTier'].upper() == utils.TenantTier.PREMIUM.value.upper()):\n            api_key = premium_tier_api_key\n        elif (tenant_details['tenantTier'].upper() == utils.TenantTier.STANDARD.value.upper()):\n            api_key = standard_tier_api_key\n        elif (tenant_details['tenantTier'].upper() == utils.TenantTier.BASIC.value.upper()):\n            api_key = basic_tier_api_key\n\n        tenant_details['apiKey'] = api_key\n\"\"\"\n\nreplace_in_file(lab6_tenant_reg_original_str, lab6_tenant_reg_update_str, \"../Lab6/server/TenantManagementService/tenant-registration.py\")\n\nlab6_tenant_mgmt_original_str = \"\"\"#'apiKey': tenant_details['apiKey'],\"\"\"\nlab6_tenant_mgmt_update_str   = \"\"\"'apiKey': tenant_details['apiKey'],\"\"\"\n\nreplace_in_file(lab6_tenant_mgmt_original_str, lab6_tenant_mgmt_update_str, \"../Lab6/server/TenantManagementService/tenant-management.py\")\n\nlab6_tenant_auth_original_str = \"\"\"        #TODO: Get API Key from tenant management table\n        #api_key = tenant_details['Item']['apiKey']\n\"\"\"\nlab6_tenant_auth_update_str = \"\"\"        api_key = tenant_details['Item']['apiKey']\"\"\"\n\nreplace_in_file(lab6_tenant_auth_original_str, lab6_tenant_auth_update_str, \"../Lab6/server/Resources/tenant_authorizer.py\")\n\nlab6_tenant_auth_original_str_2 = \"\"\"        #TODO: Assign API Key to authorizer response\n        #'apiKey': api_key,\n\"\"\"\nlab6_tenant_auth_update_str_2 = \"\"\"        'apiKey': api_key,\"\"\"\n\nreplace_in_file(lab6_tenant_auth_original_str_2, lab6_tenant_auth_update_str_2, \"../Lab6/server/Resources/tenant_authorizer.py\")\n\n"
  },
  {
    "path": "scripts/replace_function.py",
    "content": "def replace_in_file(str_to_find, replacement_str, file_name):\n    fh = open(file_name, \"r\")\n    txt = fh.read()\n    ts = txt.split(str_to_find)\n    fh.close()\n    fh = open(file_name, \"w\")\n    fh.write(ts[0] + replacement_str + ts[1])\n    fh.close()\n"
  },
  {
    "path": "scripts/run_all_labs.sh",
    "content": "#!/bin/bash\n##\n## This script aims to automatically deploy the labs\n## using the completed labs found in the Solutions folder.\n##\n\necho \"################ Running pre-req script... ################\"\ncd ../Cloud9Setup/\n./increase-disk-size.sh\n# ./pre-requisites.sh\ncd ../scripts/\necho \"################ Done running pre-req script... ################\"\n\n# echo \"################ Running labs... ################\"\n\n# #### Note that deploying lab1 is not a requirement ####\n# # echo \"################ Running lab1... ################\"\n# # cd ../Solution/Lab1/scripts\n# # ./deployment.sh -s -c --stack-name serverless-saas-workshop-lab1\n# # cd ../../../scripts/\n# # echo \"################ Done running lab1. ################\"\n# #######################################################\n\necho \"################ Running lab2... ################\"\n\ncd ../Solution/Lab2/scripts\n./deployment.sh -s -c --email syeduh+serverlesslab@amazon.com\n./deployment.sh -s\ncd ../../../scripts/\n\necho \"################ Done running lab2. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab3... ################\"\n\ncd ../Solution/Lab3/scripts\n./deployment.sh -s -c\n./deployment.sh -s\ncd ../../../scripts/\n\necho \"################ Done running lab3. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab4... ################\"\n\ncd ../Solution/Lab4/scripts\n./deployment.sh -s\ncd ../../../scripts/\n\necho \"################ Done running lab4. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab5... ################\"\n\ncd ../Solution/Lab5/scripts/\n./deployment.sh -s -c\n./deployment.sh -s\ncd ../../../scripts/\n\necho \"################ Done running lab5. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab6... ################\"\n\ncd ../Solution/Lab6/scripts/\n./deployment.sh\ncd ../../../scripts/\n\necho \"################ Done running lab6. ################\"\n"
  },
  {
    "path": "scripts/run_workshop.sh",
    "content": "#!/bin/bash\n##\n## This script aims to automatically deploy the labs\n## as outlined in this workshop here:\n## https://catalog.us-east-1.prod.workshops.aws/workshops/b0c6ad36-0a4b-45d8-856b-8a64f0ac76bb/en-US\n##\n\necho \"################ Running pre-req script... ################\"\ncd ../Cloud9Setup/\n./increase-disk-size.sh\n# ./pre-requisites.sh\ncd ../scripts/\necho \"################ Done running pre-req script... ################\"\n\n# #### Note that deploying lab1 is not a requirement ####\n# #######################################################\n\necho \"################ Running lab2... ################\"\n\ncd ../Lab2/scripts\n./deployment.sh -s -c --email syeduh+serverlesslab@amazon.com\ncd ../../scripts/\n\npython3 lab2_updates.py\n\ncd ../Lab2/scripts\n./deployment.sh -s\ncd ../../scripts/\n\necho \"################ Done running lab2. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab3... ################\"\n\ncd ../Lab3/scripts\n./deployment.sh -s -c\ncd ../../scripts/\n\npython3 lab3_updates.py\n\ncd ../Lab3/scripts\n./deployment.sh -s\ncd ../../scripts/\n\necho \"################ Done running lab3. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab4... ################\"\n\npython3 lab4_updates.py\ncd ../Lab4/scripts\n./deployment.sh -s\ncd ../../scripts/\n\necho \"################ Done running lab4. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab5... ################\"\n\ncd ../Lab5/scripts/\n./deployment.sh -s -c\ncd ../../scripts/\n\npython3 lab5_updates.py\n\ncd ../Lab5/scripts/\n./deployment.sh -s\ncd ../../scripts/\n\necho \"################ Done running lab5. ################\"\n\necho \"################ Sleeping for a minute before moving to next lab... ################\"\nsleep 60\n\necho \"################ Running lab6... ################\"\n\npython3 lab6_updates.py\ncd ../Lab6/scripts/\n./deployment.sh\ncd ../../scripts/\n\necho \"################ Done running lab6. ################\"\n"
  }
]