├── .flaskenv ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build_and_deploy.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── Pipfile ├── Pipfile.lock ├── README.md ├── app ├── __init__.py ├── api │ ├── __init__.py │ ├── resources.py │ └── security.py ├── client.py └── config.py ├── docs ├── flask-logo.png ├── project-logo.png ├── python-logo.png └── vue-logo.png ├── package.json ├── public ├── favicon.ico └── index.html ├── requirements.txt ├── run.py ├── src ├── App.vue ├── assets │ ├── flask-logo.png │ ├── logo.png │ ├── python-logo.png │ └── vue-logo.png ├── backend.js ├── components │ └── HelloWorld.vue ├── filters.js ├── main.js ├── router.js ├── store.js └── views │ ├── Api.vue │ └── Home.vue ├── tests ├── __init__.py ├── test_api.py └── test_client.py └── vue.config.js /.flaskenv: -------------------------------------------------------------------------------- 1 | # Production Environment should be set to 'production' 2 | FLASK_ENV = "development" 3 | FLASK_APP = "app" 4 | # Uncomment this to debug: 5 | # FLASK_DEBUG=1 6 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.github/workflows/build_and_deploy.yaml: -------------------------------------------------------------------------------- 1 | # For more info on Python, GitHub Actions, and Azure App Service 2 | # please head to https://aka.ms/python-webapps-actions 3 | 4 | name: Build and deploy Flask + Vue.js app to Azure App Service 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | 11 | env: 12 | WEBAPP_NAME: '' # Replace with the name of your Azure web app 13 | RESOURCE_GROUP: '' # Replace with the name of your Resource Group 14 | 15 | jobs: 16 | build-and-test: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: 3.6 26 | 27 | - name: Set up Node.js 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: 12 31 | 32 | - name: Install and build Vue.js project 33 | run: | 34 | npm install 35 | npm run build 36 | 37 | - name: Create and start virtual environment 38 | run: | 39 | python3 -m venv venv 40 | source venv/bin/activate 41 | 42 | - name: Install dependencies 43 | run: pip install -r requirements.txt 44 | 45 | - name: test with PyTest 46 | run: pytest --cov=app --cov-report=xml 47 | 48 | - name: Upload artifact for deployment jobs 49 | uses: actions/upload-artifact@v2 50 | with: 51 | name: python-app 52 | path: | 53 | . 54 | !node_modules/ 55 | !venv/ 56 | 57 | deploy-to-webapp: 58 | needs: build-and-test 59 | runs-on: ubuntu-latest 60 | 61 | steps: 62 | - uses: actions/download-artifact@v2 63 | with: 64 | name: python-app 65 | path: . 66 | 67 | - name: Log in to Azure CLI 68 | uses: azure/login@v1 69 | with: 70 | creds: ${{ secrets.AZURE_SERVICE_PRINCIPAL }} 71 | 72 | - name: Configure deployment and runtime settings on the webapp 73 | run: | 74 | az configure --defaults ${{ env.RESOURCE_GROUP }} 75 | az webapp config appsettings --name ${{ env.WEBAPP_NAME }} --settings \ 76 | SCM_DO_BUILD_DURING_DEPLOYMENT=true \ 77 | FLASK_ENV=production 78 | 79 | az webapp config set --name ${{ env.WEBAPP_NAME }} \ 80 | --startup-file "gunicorn --bind=0.0.0.0 --timeout 600 app:app" 81 | 82 | - name: Deploy to App Service 83 | uses: azure/webapps-deploy@v2 84 | with: 85 | app-name: ${{ env.WEBAPP_NAME}} 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | *.pyc 132 | .cache 133 | .mypy_cache\ 134 | .env 135 | venv/ 136 | 137 | /dist 138 | .coverage 139 | .pytest_cache/ 140 | htmlcov/ 141 | 142 | .DS_Store 143 | node_modules/ 144 | npm-debug.log* 145 | yarn-debug.log* 146 | yarn-error.log* 147 | selenium-debug.log 148 | package-lock.json 149 | 150 | # Editor directories and files 151 | .idea 152 | .vscode 153 | *.suo 154 | *.ntvs* 155 | *.njsproj 156 | *.sln 157 | *security-sgps.py 158 | *security_backend.py 159 | 160 | node_modules/* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this Flask + Vue.js sample app 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | gunicorn = "==19.7.1" 8 | flask-restplus = "*" 9 | python-dotenv = "*" 10 | flask = "*" 11 | 12 | [dev-packages] 13 | pytest = "*" 14 | bumpversion = "*" 15 | pytest-sugar = "*" 16 | pytest-cov = "*" 17 | 18 | [requires] 19 | python_version = "3.6" 20 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "86fcf75e89c8bcab95ada34b3d66e96a8fbb94949d95bf087307aa2acbd42312" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.6" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.python.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aniso8601": { 20 | "hashes": [ 21 | "sha256:547e7bc88c19742e519fb4ca39f4b8113fdfb8fca322e325f16a8bfc6cfc553c", 22 | "sha256:e7560de91bf00baa712b2550a2fdebf0188c5fce2fcd1162fbac75c19bb29c95" 23 | ], 24 | "version": "==4.0.1" 25 | }, 26 | "click": { 27 | "hashes": [ 28 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 29 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 30 | ], 31 | "version": "==7.0" 32 | }, 33 | "flask": { 34 | "hashes": [ 35 | "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", 36 | "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" 37 | ], 38 | "index": "pypi", 39 | "version": "==1.0.2" 40 | }, 41 | "flask-restplus": { 42 | "hashes": [ 43 | "sha256:3fad697e1d91dfc13c078abcb86003f438a751c5a4ff41b84c9050199d2eab62", 44 | "sha256:cdc27b5be63f12968a7f762eaa355e68228b0c904b4c96040a314ba7dc6d0e69" 45 | ], 46 | "index": "pypi", 47 | "version": "==0.12.1" 48 | }, 49 | "gunicorn": { 50 | "hashes": [ 51 | "sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6", 52 | "sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622" 53 | ], 54 | "index": "pypi", 55 | "version": "==19.7.1" 56 | }, 57 | "itsdangerous": { 58 | "hashes": [ 59 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 60 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 61 | ], 62 | "version": "==1.1.0" 63 | }, 64 | "jinja2": { 65 | "hashes": [ 66 | "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", 67 | "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" 68 | ], 69 | "version": "==2.10" 70 | }, 71 | "jsonschema": { 72 | "hashes": [ 73 | "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", 74 | "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" 75 | ], 76 | "version": "==2.6.0" 77 | }, 78 | "markupsafe": { 79 | "hashes": [ 80 | "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432", 81 | "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b", 82 | "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9", 83 | "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af", 84 | "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834", 85 | "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd", 86 | "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d", 87 | "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7", 88 | "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b", 89 | "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3", 90 | "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c", 91 | "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2", 92 | "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7", 93 | "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36", 94 | "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1", 95 | "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e", 96 | "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1", 97 | "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c", 98 | "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856", 99 | "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550", 100 | "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492", 101 | "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672", 102 | "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401", 103 | "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6", 104 | "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6", 105 | "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c", 106 | "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd", 107 | "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1" 108 | ], 109 | "version": "==1.1.0" 110 | }, 111 | "python-dotenv": { 112 | "hashes": [ 113 | "sha256:122290a38ece9fe4f162dc7c95cae3357b983505830a154d3c98ef7f6c6cea77", 114 | "sha256:4a205787bc829233de2a823aa328e44fd9996fedb954989a21f1fc67c13d7a77" 115 | ], 116 | "index": "pypi", 117 | "version": "==0.9.1" 118 | }, 119 | "pytz": { 120 | "hashes": [ 121 | "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca", 122 | "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6" 123 | ], 124 | "version": "==2018.7" 125 | }, 126 | "six": { 127 | "hashes": [ 128 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 129 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 130 | ], 131 | "version": "==1.11.0" 132 | }, 133 | "werkzeug": { 134 | "hashes": [ 135 | "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c", 136 | "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b" 137 | ], 138 | "version": "==0.14.1" 139 | } 140 | }, 141 | "develop": { 142 | "atomicwrites": { 143 | "hashes": [ 144 | "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", 145 | "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" 146 | ], 147 | "version": "==1.2.1" 148 | }, 149 | "attrs": { 150 | "hashes": [ 151 | "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", 152 | "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" 153 | ], 154 | "version": "==18.2.0" 155 | }, 156 | "bumpversion": { 157 | "hashes": [ 158 | "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", 159 | "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57" 160 | ], 161 | "index": "pypi", 162 | "version": "==0.5.3" 163 | }, 164 | "coverage": { 165 | "hashes": [ 166 | "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", 167 | "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", 168 | "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", 169 | "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", 170 | "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", 171 | "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", 172 | "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", 173 | "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", 174 | "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", 175 | "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", 176 | "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", 177 | "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", 178 | "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", 179 | "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", 180 | "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", 181 | "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", 182 | "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", 183 | "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", 184 | "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", 185 | "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", 186 | "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", 187 | "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", 188 | "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", 189 | "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", 190 | "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", 191 | "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", 192 | "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", 193 | "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", 194 | "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", 195 | "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", 196 | "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" 197 | ], 198 | "version": "==4.5.2" 199 | }, 200 | "more-itertools": { 201 | "hashes": [ 202 | "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", 203 | "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", 204 | "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" 205 | ], 206 | "version": "==4.3.0" 207 | }, 208 | "packaging": { 209 | "hashes": [ 210 | "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807", 211 | "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9" 212 | ], 213 | "version": "==18.0" 214 | }, 215 | "pluggy": { 216 | "hashes": [ 217 | "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", 218 | "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" 219 | ], 220 | "version": "==0.8.0" 221 | }, 222 | "py": { 223 | "hashes": [ 224 | "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", 225 | "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" 226 | ], 227 | "version": "==1.7.0" 228 | }, 229 | "pyparsing": { 230 | "hashes": [ 231 | "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b", 232 | "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592" 233 | ], 234 | "version": "==2.3.0" 235 | }, 236 | "pytest": { 237 | "hashes": [ 238 | "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", 239 | "sha256:e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660" 240 | ], 241 | "index": "pypi", 242 | "version": "==3.10.1" 243 | }, 244 | "pytest-cov": { 245 | "hashes": [ 246 | "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", 247 | "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762" 248 | ], 249 | "index": "pypi", 250 | "version": "==2.6.0" 251 | }, 252 | "pytest-sugar": { 253 | "hashes": [ 254 | "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283", 255 | "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab" 256 | ], 257 | "index": "pypi", 258 | "version": "==0.9.2" 259 | }, 260 | "six": { 261 | "hashes": [ 262 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 263 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 264 | ], 265 | "version": "==1.11.0" 266 | }, 267 | "termcolor": { 268 | "hashes": [ 269 | "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b" 270 | ], 271 | "version": "==1.1.0" 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-VueJs-Template 🌶️✌ 2 | 3 | > A Flask + Vue.js sample application adapted from [this repository](https://github.com/gtalarico/flask-vuejs-template). 4 | 5 | ![Vue Logo](/docs/vue-logo.png "Vue Logo") ![Flask Logo](/docs/flask-logo.png "Flask Logo") 6 | 7 | ## Features 8 | 9 | * Minimal Flask 1.0 App 10 | * [Flask-RestPlus](http://flask-restplus.readthedocs.io) API with class-based secure resource routing 11 | * Starter [PyTest](http://pytest.org) test suite 12 | * [vue-cli 3](https://github.com/vuejs/vue-cli/blob/dev/docs/README.md) + yarn 13 | * [Vuex](https://vuex.vuejs.org/) 14 | * [Vue Router](https://router.vuejs.org/) 15 | * [Axios](https://github.com/axios/axios/) for backend communication 16 | * Sample Vue [Filters](https://vuejs.org/v2/guide/filters.html) 17 | 18 | ## Template Structure 19 | 20 | The template uses Flask & Flask-RestPlus to create a minimal REST style API, 21 | and let's VueJs + vue-cli handle the front end and asset pipline. 22 | Data from the python server to the Vue application is passed by making Ajax requests. 23 | 24 | ### Application Structure 25 | 26 | #### Rest Api 27 | 28 | The Api is served using a Flask blueprint at `/api/` using Flask RestPlus class-based 29 | resource routing. 30 | 31 | #### Client Application 32 | 33 | A Flask view is used to serve the `index.html` as an entry point into the Vue app at the endpoint `/`. 34 | 35 | The template uses vue-cli 3 and assumes Vue Cli & Webpack will manage front-end resources and assets, so it does overwrite template delimiter. 36 | 37 | The Vue instance is preconfigured with Filters, Vue-Router, Vuex; each of these can easilly removed if they are not desired. 38 | 39 | #### Important Files 40 | 41 | | Location | Content | 42 | |----------------------|--------------------------------------------| 43 | | `/app` | Flask Application | 44 | | `/app/api` | Flask Rest Api (`/api`) | 45 | | `/app/client.py` | Flask Client (`/`) | 46 | | `/src` | Vue App . | 47 | | `/src/main.js` | JS Application Entry Point | 48 | | `/public/index.html` | Html Application Entry Point (`/`) | 49 | | `/public/static` | Static Assets | 50 | | `/dist/` | Bundled Assets Output (generated at `yarn build` | 51 | | [`.github/workflows/build_and_deploy.yaml`](.github/workflows/build_and_deploy.yaml) | The deployment workflow | 52 | 53 | ## Installation 54 | 55 | ### Before you start 56 | 57 | Before getting started, you should have the following installed and running: 58 | 59 | - [X] NPM - [instructions](https://yarnpkg.com/en/docs/install#mac-stable) 60 | - [X] Vue Cli 3 - [instructions](https://cli.vuejs.org/guide/installation.html) 61 | - [X] Python 3 62 | - [X] Pipenv (optional) 63 | 64 | ### Get started 65 | 66 | * Clone this repository: 67 | 68 | ```bash 69 | git clone https://github.com/Azure-Samples/flask-vuejs-webapp.git 70 | ``` 71 | 72 | * Setup virtual environment, install dependencies, and activate it: 73 | 74 | ```bash 75 | python -m venv venv 76 | source venv/bin/activate # or ".\venv\Scripts\activate" on Windows 77 | pip install -r requirements.txt 78 | ``` 79 | 80 | * Install JS dependencies 81 | 82 | ```bash 83 | npm install 84 | ``` 85 | 86 | ### Development Servers 87 | 88 | Run Flask API development server: 89 | 90 | ```bash 91 | python run.py 92 | ``` 93 | 94 | From another tab in the same directory, start the webpack dev server: 95 | 96 | ```bash 97 | npm run serve 98 | ``` 99 | 100 | The Vuejs application will be served from `localhost:8080` and the Flask API and static files will be served from `localhost:5000`. The dual dev-server setup allows you to take advantage of webpack's development server with hot module replacement. The proxy is configured in `vue.config.js` and is used to route the requests back to Flask's API on port 5000. 101 | 102 | If you would rather run a single dev server, you can run Flask's development server only on `:5000`, but you have to build build the Vue app first and the page will not reload on changes. Run the commands below to build the Vue.js app and serve it on Python's dev server. 103 | 104 | ```bash 105 | npm build 106 | python run.py 107 | ``` 108 | 109 | ## Production Server 110 | 111 | This template is configured to be built with GitHub Actions and run on Azure App Service. You can take a look at the GitHub Actions [workflow file](.github/workflows/build_and_deploy.yaml) to see the build and deployment steps. 112 | 113 | ### JS Build Process 114 | 115 | The Vue.js application is built and minified to the `dist/` folder. Once the application is deployed to App Service, Flask serves the static content via the Flask Blueprint in [`client.py`](app/client.py). 116 | 117 | ### Python Build Process 118 | 119 | The GitHub Actions workflow builds and tests the Vue.js and Flask applications before deploying. If the tests pass successfully, the Flask app is deployed to the web app along with the minified Vue.js. Once deployed, the App Service build pipeline will re-install the Python dependencies so they are correctly installed for the runtime image. 120 | 121 | ### Production Sever Setup 122 | 123 | The second job of the deployment workflow uses the Azure CLI to set some configuration settings on the web app. These are outlined below. 124 | 125 | - `SCM_DO_BUILD_DURING_DEPLOYMENT`: When set to true, the app setting will force the SCM site to re-build the Flask application. This ensures that the Python libraries are installed properly for the runtime OS. 126 | - `FLASK_ENV`: This app setting tells Flask which configuration to use. 127 | - `startup-script`: This setting tells App Service how to initialize the application. In our case, we use gunicorn to serve the "app" object in [`app.py`](app/__init__.py). For this particular application, set the startup script to `gunicorn --bind=0.0.0.0 --timeout 600 app:app`. 128 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask, current_app, send_file 3 | 4 | from .api import api_bp 5 | from .client import client_bp 6 | 7 | app = Flask(__name__, static_folder='../dist/static') 8 | app.register_blueprint(api_bp) 9 | # app.register_blueprint(client_bp) 10 | 11 | from .config import Config 12 | app.logger.info('>>> {}'.format(Config.FLASK_ENV)) 13 | 14 | @app.route('/') 15 | def index_client(): 16 | dist_dir = current_app.config['DIST_DIR'] 17 | entry = os.path.join(dist_dir, 'index.html') 18 | return send_file(entry) 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/api/__init__.py: -------------------------------------------------------------------------------- 1 | """ API Blueprint Application """ 2 | 3 | from flask import Blueprint, current_app 4 | from flask_restplus import Api 5 | 6 | api_bp = Blueprint('api_bp', __name__, url_prefix='/api') 7 | api_rest = Api(api_bp) 8 | 9 | 10 | @api_bp.after_request 11 | def add_header(response): 12 | response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization' 13 | return response 14 | 15 | 16 | # Import resources to ensure view is registered 17 | from .resources import * # NOQA 18 | -------------------------------------------------------------------------------- /app/api/resources.py: -------------------------------------------------------------------------------- 1 | """ 2 | REST API Resource Routing 3 | http://flask-restplus.readthedocs.io 4 | """ 5 | 6 | from datetime import datetime 7 | from flask import request 8 | from flask_restplus import Resource 9 | 10 | from .security import require_auth 11 | from . import api_rest 12 | 13 | 14 | class SecureResource(Resource): 15 | """ Calls require_auth decorator on all requests """ 16 | method_decorators = [require_auth] 17 | 18 | 19 | @api_rest.route('/resource/') 20 | class ResourceOne(Resource): 21 | """ Unsecure Resource Class: Inherit from Resource """ 22 | 23 | def get(self, resource_id): 24 | timestamp = datetime.utcnow().isoformat() 25 | return {'timestamp': timestamp} 26 | 27 | def post(self, resource_id): 28 | json_payload = request.json 29 | return {'timestamp': json_payload}, 201 30 | 31 | 32 | @api_rest.route('/secure-resource/') 33 | class SecureResourceOne(SecureResource): 34 | """ Unsecure Resource Class: Inherit from Resource """ 35 | 36 | def get(self, resource_id): 37 | timestamp = datetime.utcnow().isoformat() 38 | return {'timestamp': timestamp} 39 | -------------------------------------------------------------------------------- /app/api/security.py: -------------------------------------------------------------------------------- 1 | """ Security Related things """ 2 | from functools import wraps 3 | from flask import request 4 | from flask_restplus import abort 5 | 6 | 7 | def require_auth(func): 8 | """ Secure method decorator """ 9 | @wraps(func) 10 | def wrapper(*args, **kwargs): 11 | # Verify if User is Authenticated 12 | # Authentication logic goes here 13 | if request.headers.get('authorization'): 14 | return func(*args, **kwargs) 15 | else: 16 | return abort(401) 17 | return wrapper 18 | -------------------------------------------------------------------------------- /app/client.py: -------------------------------------------------------------------------------- 1 | """ Client App """ 2 | 3 | import os 4 | from flask import Blueprint, render_template 5 | 6 | client_bp = Blueprint('client_app', __name__, 7 | url_prefix='', 8 | static_url_path='', 9 | static_folder='./dist/static/', 10 | template_folder='./dist/', 11 | ) 12 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Global Flask Application Setting 3 | 4 | See `.flaskenv` for default settings. 5 | """ 6 | 7 | import os 8 | from app import app 9 | 10 | 11 | class Config(object): 12 | # If not set fall back to production for safety 13 | FLASK_ENV = os.getenv('FLASK_ENV', 'production') 14 | # Set FLASK_SECRET on your production Environment 15 | SECRET_KEY = os.getenv('FLASK_SECRET', 'Secret') 16 | 17 | APP_DIR = os.path.dirname(__file__) 18 | ROOT_DIR = os.path.dirname(APP_DIR) 19 | DIST_DIR = os.path.join(ROOT_DIR, 'dist') 20 | 21 | if not os.path.exists(DIST_DIR): 22 | raise Exception( 23 | 'DIST_DIR not found: {}'.format(DIST_DIR)) 24 | 25 | app.config.from_object('app.config.Config') 26 | -------------------------------------------------------------------------------- /docs/flask-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/docs/flask-logo.png -------------------------------------------------------------------------------- /docs/project-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/docs/project-logo.png -------------------------------------------------------------------------------- /docs/python-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/docs/python-logo.png -------------------------------------------------------------------------------- /docs/vue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/docs/vue-logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue_app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --open", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.18.0", 12 | "vue": "^2.5.13", 13 | "vue-router": "^3.0.1", 14 | "vuex": "^3.0.1" 15 | }, 16 | "devDependencies": { 17 | "@vue/cli-plugin-babel": "^3.0.0-beta.6", 18 | "@vue/cli-plugin-eslint": "^3.0.0-beta.6", 19 | "@vue/cli-service": "^3.0.0-beta.6", 20 | "@vue/eslint-config-standard": "^3.0.0-beta.6", 21 | "lint-staged": "^6.0.0", 22 | "node-sass": "^4.7.2", 23 | "sass-loader": "^6.0.6", 24 | "vue-template-compiler": "^2.5.13" 25 | }, 26 | "babel": { 27 | "presets": [ 28 | "@vue/app" 29 | ] 30 | }, 31 | "eslintConfig": { 32 | "root": true, 33 | "extends": [ 34 | "plugin:vue/essential", 35 | "@vue/standard" 36 | ] 37 | }, 38 | "postcss": { 39 | "plugins": { 40 | "autoprefixer": {} 41 | } 42 | }, 43 | "browserslist": [ 44 | "> 1%", 45 | "last 2 versions", 46 | "not ie <= 8" 47 | ], 48 | "gitHooks": { 49 | "pre-commit": "lint-staged" 50 | }, 51 | "lint-staged": { 52 | "*.js": [ 53 | "vue-cli-service lint", 54 | "git add" 55 | ], 56 | "*.vue": [ 57 | "vue-cli-service lint", 58 | "git add" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Flask + Vue.js Template 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/requirements.txt -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import os 2 | from app import app 3 | 4 | app.run(port=5000) 5 | 6 | # To Run: 7 | # python run.py 8 | # or 9 | # python -m flask run 10 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 49 | -------------------------------------------------------------------------------- /src/assets/flask-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/src/assets/flask-logo.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/python-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/src/assets/python-logo.png -------------------------------------------------------------------------------- /src/assets/vue-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/src/assets/vue-logo.png -------------------------------------------------------------------------------- /src/backend.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | let $axios = axios.create({ 4 | baseURL: '/api/', 5 | timeout: 5000, 6 | headers: {'Content-Type': 'application/json'} 7 | }) 8 | 9 | // Request Interceptor 10 | $axios.interceptors.request.use(function (config) { 11 | config.headers['Authorization'] = 'Fake Token' 12 | return config 13 | }) 14 | 15 | // Response Interceptor to handle and log errors 16 | $axios.interceptors.response.use(function (response) { 17 | return response 18 | }, function (error) { 19 | // Handle Error 20 | console.log(error) 21 | return Promise.reject(error) 22 | }) 23 | 24 | export default { 25 | 26 | fetchResource () { 27 | return $axios.get(`resource/xxx`) 28 | .then(response => response.data) 29 | }, 30 | 31 | fetchSecureResource () { 32 | return $axios.get(`secure-resource/zzz`) 33 | .then(response => response.data) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 40 | 41 | 42 | 58 | -------------------------------------------------------------------------------- /src/filters.js: -------------------------------------------------------------------------------- 1 | // Vue.js Filters 2 | // https://vuejs.org/v2/guide/filters.html 3 | 4 | import Vue from 'vue' 5 | 6 | let filters = { 7 | 8 | formatTimestamp (timestamp) { 9 | let datetime = new Date(timestamp) 10 | return datetime.toLocaleTimeString('en-US') 11 | } 12 | } 13 | 14 | // Register All Filters on import 15 | Object.keys(filters).forEach(function (filterName) { 16 | Vue.filter(filterName, filters[filterName]) 17 | }) 18 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | import './filters' 7 | 8 | Vue.config.productionTip = false 9 | 10 | new Vue({ 11 | router, 12 | store, 13 | render: h => h(App) 14 | }).$mount('#app') 15 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | import Api from './views/Api.vue' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'home', 13 | component: Home 14 | }, 15 | { 16 | path: '/api', 17 | name: 'api', 18 | component: Api 19 | } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | 9 | }, 10 | mutations: { 11 | 12 | }, 13 | actions: { 14 | 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /src/views/Api.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/flask-vuejs-webapp/1b5c864aecbe88d051fab0e9fb798784ae9af2f3/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | """ pytests for Flask """ 2 | 3 | import pytest 4 | from app import app 5 | 6 | @pytest.fixture(scope="module") 7 | def client(): 8 | app.config['TESTING'] = True 9 | return app.test_client() 10 | 11 | def test_api(client): 12 | resp = client.get('/api/') 13 | assert resp.status_code == 200 14 | 15 | def test_resource_one(client): 16 | resp = client.get('/api/resource/one') 17 | assert resp.status_code == 200 18 | 19 | def test_resource_one_post(client): 20 | resp = client.post('/api/resource/one') 21 | assert resp.status_code == 201 22 | 23 | def test_resource_one_patch(client): 24 | resp = client.patch('/api/resource/one') 25 | assert resp.status_code == 405 26 | 27 | def test_secure_resource_fail(client): 28 | resp = client.get('/api/secure-resource/two') 29 | assert resp.status_code == 401 30 | 31 | def test_secure_resource_pass(client): 32 | resp = client.get('/api/secure-resource/two', 33 | headers={'authorization': 'Bearer x'}) 34 | assert resp.status_code == 200 35 | 36 | @pytest.fixture(scope="module") 37 | def request_context(): 38 | return app.test_request_context('') 39 | 40 | def test_session(request_context): 41 | with request_context: 42 | # Do something that requires request context 43 | assert True 44 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | """ pytests for Flask """ 2 | 3 | import pytest 4 | from app import app 5 | 6 | @pytest.fixture(scope="module") 7 | def client(): 8 | app.config['TESTING'] = True 9 | return app.test_client() 10 | 11 | def test_api(client): 12 | resp = client.get('/') 13 | assert resp.status_code == 200 14 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // const IS_PRODUCTION = process.env.NODE_ENV === 'production' 2 | 3 | module.exports = { 4 | outputDir: 'dist', 5 | assetsDir: 'static', 6 | // baseUrl: IS_PRODUCTION 7 | // ? 'http://cdn123.com' 8 | // : '/', 9 | // For Production, replace set baseUrl to CDN 10 | // And set the CDN origin to `yourdomain.com/static` 11 | // Whitenoise will serve once to CDN which will then cache 12 | // and distribute 13 | devServer: { 14 | proxy: { 15 | '/api*': { 16 | // Forward frontend dev server request for /api to flask dev server 17 | target: 'http://localhost:5000/' 18 | } 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------