├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── art ├── angular-django-seed-project.png ├── angular-django-seed-project.psd ├── client-trigger.jpg ├── cloud_dns.jpg └── server-trigger.jpg ├── client ├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── angular.json ├── browserslist ├── cloudbuild.yaml ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.json ├── karma.conf.js ├── ngsw-config.json ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── modules │ │ │ ├── material │ │ │ │ └── material.module.ts │ │ │ └── users │ │ │ │ ├── actions │ │ │ │ └── user.actions.ts │ │ │ │ ├── components │ │ │ │ └── users-list │ │ │ │ │ ├── users-list.component.html │ │ │ │ │ ├── users-list.component.scss │ │ │ │ │ ├── users-list.component.spec.ts │ │ │ │ │ └── users-list.component.ts │ │ │ │ ├── effects │ │ │ │ └── user.effects.ts │ │ │ │ ├── models │ │ │ │ └── user.model.ts │ │ │ │ ├── reducers │ │ │ │ ├── index.ts │ │ │ │ └── user.reducer.ts │ │ │ │ ├── services │ │ │ │ ├── user.service.spec.ts │ │ │ │ └── user.service.ts │ │ │ │ ├── users-routing.module.ts │ │ │ │ └── users.module.ts │ │ └── reducers │ │ │ └── index.ts │ ├── assets │ │ ├── .gitkeep │ │ └── icons │ │ │ ├── icon-128x128.png │ │ │ ├── icon-144x144.png │ │ │ ├── icon-152x152.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-384x384.png │ │ │ ├── icon-512x512.png │ │ │ ├── icon-72x72.png │ │ │ └── icon-96x96.png │ ├── assp-theme.scss │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── manifest.webmanifest │ ├── polyfills.ts │ ├── service-worker.js │ ├── styles.scss │ ├── test.ts │ └── tsconfig.json ├── sw-precache-config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ├── codecov.yml ├── database └── autopgsqlbackup └── server ├── .flake8 ├── Dockerfile ├── Pipfile ├── Pipfile.lock ├── README.md ├── api ├── __init__.py ├── tests.py └── users │ ├── __init__.py │ ├── model.py │ ├── routes.py │ ├── tests.py │ └── view.py ├── cloudbuild.yaml ├── config ├── local_settings.template ├── settings.py ├── urls.py └── wsgi.py ├── kubernetes ├── helm-permissions.yaml └── server.yaml ├── main.py ├── manage.py ├── requirements.txt └── util ├── __init__.py ├── config.py └── seed.py /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at nir@galon.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | So, you want to start contribute, Great! 4 | 5 | 1. First step is to clone the repo (`git clone https://github.com/nirgn975/Angular-Sanic-Seed-Project.git`) 6 | 7 | 2. Take yourself an issue (or talk with a team member to get one). Create a new branch (`git branch -b -`), and start working on it. 8 | 9 | Example of a branch name `18-fix_server_docker`. 10 | 11 | 3. After you do your first commit create a PR (Pull Request) (you can do it from github GUI, Pycharm/Webstorm under _VCS -> Git -> Create Pull Request_, atom/ vscode, or even the command line) and if you don't finish the PR yet (what I assume is true because it's only the first commit) write `WIP` (stand for "Work In Progress") in the title. 12 | 13 | Example: `WIP: Create Docker image..` 14 | Please add a link to the issue in the body of the PR (write hashtag with the issue number). 15 | Example: `#11` 16 | 17 | 4. Before you finish you PR (Pull Request) make sure all the tests are pass in `Travis`. If some of them are failed, fix your code or tests (depends on the issue). 18 | 19 | 5. When you finish, remove the `WIP` from the title and assign it to `@nirgn975`, and write some text like "@nirgn975 Ready for review" (when you mention him like that, GitHub will send a notification to that person). 20 | 21 | 6. Please go through [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) and [Angular Style Guide](https://angular.io/styleguide) before submitting a PR. 22 | 23 | 7. Wait for code review and HAVE FUN! 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **This is a ...** (check one with "x") 2 | ``` 3 | [ ] Bug report 4 | [ ] Feature request 5 | [ ] Clarification on the documentation 6 | ``` 7 | 8 | **Current behavior** 9 | 10 | 11 | **What is the expected behavior?** (if you know how it should behave) 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What kind of change does this PR introduce?** (check _one_ with "x") 2 | ``` 3 | [ ] Bugfix 4 | [ ] Feature 5 | [ ] Code style update (formatting, local variables) 6 | [ ] Refactoring (no functional changes, no api changes) 7 | [ ] Build related changes 8 | [ ] CI related changes 9 | [ ] Docs related changes 10 | [ ] Other... Please describe: 11 | ``` 12 | 13 | **Link to the open issue**: 14 | 15 | 16 | **Other information**: 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | ======= 3 | 4 | >>>>>>> b72a69f (Update dependencies (#20)) 5 | # Created by https://www.gitignore.io/api/python,django,node,sass,virtualenv,jetbrains 6 | 7 | ### Python ### 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *,cover 54 | .hypothesis/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 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 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv/ 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | 101 | ### Django ### 102 | *.pyc 103 | db.sqlite3 104 | media 105 | 106 | 107 | ### Node ### 108 | # Logs 109 | logs 110 | npm-debug.log* 111 | 112 | # Runtime data 113 | pids 114 | *.pid 115 | *.seed 116 | *.pid.lock 117 | 118 | # Directory for instrumented libs generated by jscoverage/JSCover 119 | lib-cov 120 | 121 | # Coverage directory used by tools like istanbul 122 | coverage 123 | 124 | # nyc test coverage 125 | .nyc_output 126 | 127 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 128 | .grunt 129 | 130 | # node-waf configuration 131 | .lock-wscript 132 | 133 | # Compiled binary addons (http://nodejs.org/api/addons.html) 134 | build/Release 135 | 136 | # Dependency directories 137 | node_modules 138 | jspm_packages 139 | 140 | # Optional npm cache directory 141 | .npm 142 | 143 | # Optional eslint cache 144 | .eslintcache 145 | 146 | # Optional REPL history 147 | .node_repl_history 148 | 149 | # Output of 'npm pack' 150 | *.tgz 151 | 152 | # Yarn Integrity file 153 | .yarn-integrity 154 | 155 | 156 | 157 | ### Sass ### 158 | .sass-cache/ 159 | *.css.map 160 | 161 | 162 | ### VirtualEnv ### 163 | # Virtualenv 164 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 165 | [Bb]in 166 | [Ii]nclude 167 | [Ll]ib 168 | [Ll]ib64 169 | [Ll]ocal 170 | [Ss]cripts 171 | pyvenv.cfg 172 | .venv 173 | pip-selfcheck.json 174 | 175 | 176 | ### JetBrains ### 177 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 178 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 179 | 180 | # User-specific stuff: 181 | .idea/workspace.xml 182 | .idea/tasks.xml 183 | 184 | # Sensitive or high-churn files: 185 | .idea/dataSources/ 186 | .idea/dataSources.ids 187 | .idea/dataSources.xml 188 | .idea/dataSources.local.xml 189 | .idea/sqlDataSources.xml 190 | .idea/dynamic.xml 191 | .idea/uiDesigner.xml 192 | 193 | # Gradle: 194 | .idea/gradle.xml 195 | .idea/libraries 196 | 197 | # Mongo Explorer plugin: 198 | .idea/mongoSettings.xml 199 | 200 | ## File-based project format: 201 | *.iws 202 | 203 | ## Plugin-specific files: 204 | 205 | # IntelliJ 206 | /out/ 207 | 208 | # mpeltonen/sbt-idea plugin 209 | .idea_modules/ 210 | 211 | # JIRA plugin 212 | atlassian-ide-plugin.xml 213 | 214 | # Crashlytics plugin (for Android Studio and IntelliJ) 215 | com_crashlytics_export_strings.xml 216 | crashlytics.properties 217 | crashlytics-build.properties 218 | fabric.properties 219 | 220 | ### JetBrains Patch ### 221 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 222 | 223 | # *.iml 224 | # modules.xml 225 | # .idea/misc.xml 226 | # *.ipr 227 | 228 | <<<<<<< HEAD 229 | 230 | ### OSX ### 231 | *.DS_Store 232 | .AppleDouble 233 | .LSOverride 234 | 235 | # Icon must end with two \r 236 | Icon 237 | # Thumbnails 238 | ._* 239 | # Files that might appear in the root of a volume 240 | .DocumentRevisions-V100 241 | .fseventsd 242 | .Spotlight-V100 243 | .TemporaryItems 244 | .Trashes 245 | .VolumeIcon.icns 246 | .com.apple.timemachine.donotpresent 247 | # Directories potentially created on remote AFP share 248 | .AppleDB 249 | .AppleDesktop 250 | Network Trash Folder 251 | Temporary Items 252 | .apdisk 253 | 254 | ======= 255 | >>>>>>> b72a69f (Update dependencies (#20)) 256 | # End of https://www.gitignore.io/api/python,django,node,sass,virtualenv,jetbrains 257 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | matrix: 3 | include: 4 | - language: python 5 | python: 3.7 6 | env: 7 | - DB_URI="postgres://postgres:@localhost:5432/assp" 8 | - DB_NAME="assp" 9 | - DB_USER="postgres" 10 | - DB_PASSWORD="" 11 | - DB_PORT="5432" 12 | - HOST="0.0.0.0" 13 | - PORT="8000" 14 | - LOG_LEVEL="info" 15 | - ENV="development" 16 | services: 17 | - postgresql 18 | before_script: 19 | - cd server && pip install coverage pycodestyle python-dotenv 20 | - pip install -r requirements.txt 21 | - psql -c 'create database assp;' -U postgres 22 | script: 23 | - python -m unittest 24 | - pycodestyle --show-source --max-line-length=120 --show-pep8 . 25 | 26 | - language: node_js 27 | node_js: 28 | - v12 29 | services: 30 | - xvfb 31 | addons: 32 | chrome: stable 33 | before_install: 34 | - npm install -g npm@latest codecov 35 | - export DISPLAY=:99.0 36 | install: 37 | - cd client && npm install 38 | script: 39 | - npm run lint 40 | - npm run test 41 | - npm run build 42 | - npm run e2e 43 | ======= 44 | sudo: required 45 | dist: trusty 46 | 47 | addons: 48 | apt: 49 | sources: 50 | - google-chrome 51 | packages: 52 | - google-chrome-stable 53 | 54 | language: node_js 55 | node_js: 56 | - "6" 57 | - "7" 58 | 59 | services: 60 | - postgresql 61 | 62 | cache: 63 | npm: true 64 | directories: 65 | - client/node_modules 66 | 67 | before_install: 68 | - npm install -g npm@latest local-web-server codecov 69 | - export CHROME_BIN=chromium-browser 70 | - export DISPLAY=:99.0 71 | - sh -e /etc/init.d/xvfb start 72 | 73 | install: 74 | # Client 75 | - cd client && npm install 76 | 77 | # Server 78 | - cd ../server && pip install coverage pycodestyle 79 | - pip install -r requirements.txt 80 | - psql -c 'create database server;' -U postgres 81 | 82 | script: 83 | # Client 84 | - cd ../client && npm run lint 85 | - npm run test 86 | - ng build --prod --aot 87 | - cd dist && ws --port 4200& 88 | - npm run e2e 89 | 90 | # Server 91 | - cd ../server && coverage run --source='.' manage.py test 92 | - coverage xml 93 | - pycodestyle --show-source --max-line-length=119 --show-pep8 . 94 | - python manage.py test 95 | 96 | after_script: 97 | - cd .. && codecov 98 | >>>>>>> b72a69f (Update dependencies (#20)) 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Nir Galon and contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Open Knesset nor the names of its contributors may be used 15 | to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | 3 | 4 | [![license][license-image]][license-url] [![GitHub release][github-image]][github-url] [![Build Status][travis-image]][travis-url] [![codecov][codecov-image]][codecov-url] [![Dependency Status][dependencyci-image]][dependencyci-url] [![Codacy Badge][codacy-image]][codacy-url] [![Maintenance][maintenance-image]][maintenance-url] [![Donate][donate-image]][donate-url] 5 | 6 | This repo is a production ready seed project. The app shows a list of users. 7 | 8 | ## Structure 9 | 10 | * The `client` contain an [Angular](https://angular.io/) app, built with [Angular-Cli](https://github.com/angular/angular-cli), and [ngrx](https://github.com/ngrx) to handle state, [Angular Material](https://github.com/angular/material2) as a design library, and have service worker, and `AOT` compiled. The app shows the users from the Sanic api. 11 | * The `server` contain a simple [Sanic](https://sanic.readthedocs.io) app that expose an `api` of `users`. The Python serve through a [gunicorn](http://gunicorn.org/) server installed in the container. 12 | * There is a `postgres` service for the database. The `database` directory contains the automatic backup script. 13 | * All the logs are going to `stdout` and can be collected through any service. 14 | * There are built in test that configured to run on `travis-ci`, and a code coverage analysis via `codecov`. 15 | ======= 16 | # An opinionated Angular - Django cluster 17 | 18 | [![license][license-image]][license-url] [![Build Status][travis-image]][travis-url] [![codecov][codecov-image]][codecov-url] [![Python 3][python3-image]][python3-url] [![Updates][updates-image]][updates-url] [![Dependency Status][dependencyci-image]][dependencyci-url] [![Donate][donate-image]][donate-url] 19 | 20 | The `server` directory contain a simple [Django](https://www.djangoproject.com/) app that expose an `api` of Django `users` with [Django REST framework](http://www.django-rest-framework.org/). The `client` directory contain an [Angular](https://angular.io/) simple app, built with [Angular-Cli](https://github.com/angular/angular-cli), [ngrx](https://github.com/ngrx) to handle state, [Angular Material](https://github.com/angular/material2) as a design library, have service worker, and ready to `AOT` compilation. The simple Angular app show the users from the Django api. 21 | 22 | The repo is a production ready app, that uses `nginx` to serve static files (the client app and static files from the server), and `gunicorn` for the server (python) stuff. All the parts are in a separate [Docker](https://www.docker.com/) containers and we use [kubernetes](https://kubernetes.io/) to manage them. 23 | 24 | ## Pre Requirements 25 | 26 | 1. install [docker](https://www.docker.com/). 27 | 2. Don't know yet. 28 | 29 | ## Installation 30 | 31 | Automatic installation of the project with docker, for development. 32 | 33 | 1. Run `$ docker-compose up` to build the docker images and run them. 34 | 3. Open the browser at [http://localhost:8000](http://localhost:8000) to see your Django (server) app. 35 | 3. Open the browser at [http://localhost:4200](http://localhost:4200) to see your Angular (client) app. 36 | >>>>>>> b72a69f (Update dependencies (#20)) 37 | 38 | The `client` app is built via the cloud build CI on GCP and deployed to the GCP `storage`. 39 | The `server` app is built via the cloud build CI as a docker image and deployed to a `GKE cluster` on GCP (managed by Kubernetes). 40 | The PostgreSQL `database` is built via the cloud build CI as a docker image and deployed to a `GKE cluster` on GCP (managed by Kubernetes). 41 | 42 | ## Production Installation 43 | 44 | Deploy the `client` app: 45 | 1. Create a storage bucket with the name of the Domain you have. 46 | 2. Create a cloud build trigger with the parameters in the [screenshot](art/client-trigger.jpg) (change the `_REGION_NAME` to the location of the bucket you created in the previous step). 47 | 3. Now you can deploy your `client` app by creating a new tag in the `v0.0.1/prod/prod` format and push it to github (`git push --tags`). 48 | 49 | Deploy the `server` app: 50 | 1. Create a `GKE` cluster on GCP. 51 | 2. Create a cloud build trigger with the parameters in the [screenshot](art/servier-trigger.jpg) (change the `_REGION_NAME` to the location of the `GKE` cluster you created in step 1). 52 | 3. Connect to the `GKE` cluster using `gcloud container clusters get-credentials prod` and then create a `tiler` using the commands: 53 | 1. `kubectl create serviceaccount --namespace kube-system tiller` 54 | 2. `kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller` 55 | 3. `kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'` 56 | 4. `helm init --service-account tiller --upgrade` 57 | 4. Then change the `helm` permissions by navigate to `server/kubernetes` in the command line and then write `kubectl apply -f helm-permissions.yaml`. 58 | 5. Now you can deploy your `server` app by create a new tag in the `v0.0.1/prod/prod` format and push it to github (`git push --tags`). 59 | 60 | Create a Cloud DNS record: 61 | 1. Create a Cloud DNS record on GCP. In this record you should add an `A` record to the kubernetes cluster (the server) and place there your load balancer ip address you get in the "Deploy the `server` app", and a `CNAME` record to our Storage bucket (client app) [screenshot](art/cloud_dns.jpg). 62 | 63 | ## Our Stack 64 | 65 | <<<<<<< HEAD 66 | * [Angular](https://angular.io/) 67 | * [Sanic](https://www.djangoproject.com/) 68 | * [PostgreSQL](https://sanic.readthedocs.io) 69 | ======= 70 | * [Angular 2.3](https://angular.io/) 71 | * [Django 1.10.5](https://www.djangoproject.com/) 72 | * [PostgreSQL](http://www.postgresql.org/) 73 | >>>>>>> b72a69f (Update dependencies (#20)) 74 | * [Docker](https://www.docker.com/) 75 | 76 | **Tools we use** 77 | 78 | <<<<<<< HEAD 79 | * [Angular Material](https://material.angular.io) 80 | * [ngrx](https://github.com/ngrx) 81 | * [Peewee](http://docs.peewee-orm.com) 82 | * [Kubernetes](https://kubernetes.io) 83 | * [Travis-CI](https://travis-ci.org) 84 | * [Codecov](https://codecov.io) 85 | 86 | ## Tests 87 | 88 | There is already tests for the `server` and the `client`, we currently at **+90** percent coverage. 89 | 90 | To run the `client` tests and lint run the commands below in the `client` directory. 91 | 92 | ``` 93 | npm run lint 94 | npm run test 95 | ``` 96 | 97 | To run the `server` tests and lint run the commands below in the `server` directory. 98 | 99 | ``` 100 | pycodestyle --show-source --max-line-length=120 --show-pep8 . 101 | python manage.py test 102 | ``` 103 | 104 | ## Load Tests 105 | 106 | We also write some tests for doing load test with [locust](http://locust.io/), you can find it under `server/locustfile.py`. 107 | 108 | To do a load test just install locust (it's in the `requirements.txt` file) go to `server` directory and run 109 | 110 | ``` 111 | locust --host=http://localhost 112 | ``` 113 | 114 | Then open up Locust’s web interface [http://localhost:8089](http://localhost:8089). 115 | 116 | ## Rolling Updates 117 | 118 | To update any of the containers that are in a service with a new image just create a new image, for example 119 | 120 | ``` 121 | docker build -t server:v2 . 122 | ``` 123 | 124 | And then update the service with the new image 125 | 126 | ``` 127 | docker service update --image server:v2 prod_server 128 | ``` 129 | 130 | ## Database Backups 131 | 132 | Each day a backup of the PostgreSQL database will be created. The daily backups are rotated weekly, so maximum 7 backup files will be at the daily directory at once. 133 | 134 | Each Saturday morning a weekly backup will be created at the weekly directory. The weekly backups are rotated on a 5 week cycle. 135 | 136 | Each month at the 1st of the month a monthly backup will be created at the monthly directory. Monthly backups are **NOT** rotated 137 | 138 | The backups are saved at `/var/backups/postgres` at the host machine via a shared volume. It can be configured in the `docker-compose.yml` at `volumes` section of the `database` service. 139 | ======= 140 | * [Angular Material](https://material.angular.io/) 141 | * [ngrx](https://github.com/ngrx) 142 | * [Django REST framework](http://www.django-rest-framework.org/) 143 | * [kubernetes](https://kubernetes.io/) 144 | >>>>>>> b72a69f (Update dependencies (#20)) 145 | 146 | ## Contribute 147 | 148 | Just fork and do a pull request (; 149 | 150 | [license-image]: https://img.shields.io/badge/license-ISC-blue.svg 151 | <<<<<<< HEAD 152 | [license-url]: https://github.com/nirgn975/Angular-Django-Seed-Project/blob/master/LICENSE 153 | [github-image]: https://img.shields.io/github/release/nirgn975/Angular-Django-Seed-Project.svg 154 | [github-url]: https://github.com/nirgn975/Angular-Django-Seed-Project/releases 155 | [travis-image]: https://travis-ci.org/nirgn975/Angular-Django-Seed-Project.svg?branch=master 156 | [travis-url]: https://travis-ci.org/nirgn975/Angular-Django-Seed-Project 157 | [codecov-image]: https://codecov.io/gh/nirgn975/Angular-Django-Seed-Project/branch/master/graph/badge.svg 158 | [codecov-url]: https://codecov.io/gh/nirgn975/Angular-Django-Seed-Project 159 | [dependencyci-image]: https://dependencyci.com/github/nirgn975/Angular-Django-Seed-Project/badge 160 | [dependencyci-url]: https://dependencyci.com/github/nirgn975/Angular-Django-Seed-Project 161 | [codacy-image]: https://api.codacy.com/project/badge/Grade/cdf4939e98804872b377a4120a4f4571 162 | [codacy-url]: https://www.codacy.com/app/nirgn975/Angular-Django-Seed-Project?utm_source=github.com&utm_medium=referral&utm_content=nirgn975/Angular-Django-Seed-Project&utm_campaign=Badge_Grade 163 | [maintenance-image]: https://img.shields.io/maintenance/yes/2019.svg 164 | [maintenance-url]: https://github.com/nirgn975 165 | [donate-image]: https://img.shields.io/badge/PayPal-Donate-lightgrey.svg 166 | ======= 167 | [license-url]: https://github.com/nirgn975/Angular2-Django-cluster/blob/master/LICENSE 168 | [travis-image]: https://travis-ci.org/nirgn975/Angular2-Django-cluster.svg?branch=master 169 | [travis-url]: https://travis-ci.org/nirgn975/Angular2-Django-cluster 170 | [codecov-image]: https://codecov.io/gh/nirgn975/Angular2-Django-cluster/branch/master/graph/badge.svg 171 | [codecov-url]: https://codecov.io/gh/nirgn975/Angular2-Django-cluster 172 | [python3-image]: https://pyup.io/repos/github/nirgn975/angular2-django-cluster/python-3-shield.svg 173 | [python3-url]: https://pyup.io/repos/github/nirgn975/angular2-django-cluster/ 174 | [updates-image]: https://pyup.io/repos/github/nirgn975/angular2-django-cluster/shield.svg 175 | [updates-url]: https://pyup.io/repos/github/nirgn975/angular2-django-cluster/ 176 | [dependencyci-image]: https://dependencyci.com/github/nirgn975/Angular2-Django-cluster/badge 177 | [dependencyci-url]: https://dependencyci.com/github/nirgn975/Angular2-Django-cluster 178 | [donate-image]: https://img.shields.io/badge/Donate-PayPal-lightgrey.svg 179 | >>>>>>> b72a69f (Update dependencies (#20)) 180 | [donate-url]: https://www.paypal.me/nirgn/2 181 | -------------------------------------------------------------------------------- /art/angular-django-seed-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/art/angular-django-seed-project.png -------------------------------------------------------------------------------- /art/angular-django-seed-project.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/art/angular-django-seed-project.psd -------------------------------------------------------------------------------- /art/client-trigger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/art/client-trigger.jpg -------------------------------------------------------------------------------- /art/cloud_dns.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/art/cloud_dns.jpg -------------------------------------------------------------------------------- /art/server-trigger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/art/server-trigger.jpg -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # IDEs and editors 11 | /.idea 12 | .project 13 | .classpath 14 | .c9/ 15 | *.launch 16 | .settings/ 17 | 18 | # IDE - VSCode 19 | .vscode/* 20 | !.vscode/settings.json 21 | !.vscode/tasks.json 22 | !.vscode/launch.json 23 | !.vscode/extensions.json 24 | 25 | # misc 26 | /.sass-cache 27 | /connect.lock 28 | /coverage/* 29 | /libpeerconnection.log 30 | npm-debug.log 31 | testem.log 32 | /typings 33 | 34 | # e2e 35 | /e2e/*.js 36 | /e2e/*.map 37 | 38 | #System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | <<<<<<< HEAD 4 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.7. 5 | ======= 6 | The front end (client) side of the project written in Angular, with Angular-CLI, ngrx, and material2. 7 | 8 | ## Prerequisites 9 | >>>>>>> b72a69f (Update dependencies (#20)) 10 | 11 | ## Development server 12 | 13 | <<<<<<< HEAD 14 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 15 | 16 | ## Code scaffolding 17 | 18 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 19 | 20 | ## Build 21 | 22 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 23 | 24 | ## Running unit tests 25 | 26 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 27 | 28 | ## Running end-to-end tests 29 | 30 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 31 | 32 | ## Further help 33 | 34 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 35 | ======= 36 | ## Manual Installation 37 | 38 | 1. Run `npm install` to install dependencies 39 | 2. Run `npm start` to fire up the dev server 40 | 3. Open browser to [http://localhost:4200](http://localhost:4200) 41 | 42 | ## Tests 43 | 44 | * Run `npm run lint` to check for TSLint mistakes 45 | * Run `npm test` to execute the unit tests via Karma 46 | * Run `npm run e2e` to execute the end-to-end tests via Protractor 47 | 48 | ## Deploy 49 | 50 | 1. Run `ng build -prod -aot` to build the project 51 | 2. Run `npm run sw` to generate the service worker file (in dist directory) 52 | >>>>>>> b72a69f (Update dependencies (#20)) 53 | -------------------------------------------------------------------------------- /client/angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.28.3", 4 | "name": "client" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.json", 19 | "prefix": "app", 20 | "styles": [ 21 | "styles.scss" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "e2e": { 32 | "protractor": { 33 | "config": "./protractor.conf.js" 34 | } 35 | }, 36 | "lint": [ 37 | { 38 | "files": "src/**/*.ts", 39 | "project": "src/tsconfig.json" 40 | }, 41 | { 42 | "files": "e2e/**/*.ts", 43 | "project": "e2e/tsconfig.json" 44 | } 45 | ], 46 | "test": { 47 | "karma": { 48 | "config": "./karma.conf.js" 49 | } 50 | }, 51 | "defaults": { 52 | "styleExt": "scss", 53 | "prefixInterfaces": false, 54 | "inline": { 55 | "style": false, 56 | "template": false 57 | }, 58 | "spec": { 59 | "class": false, 60 | "component": true, 61 | "directive": true, 62 | "module": false, 63 | "pipe": true, 64 | "service": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "client": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "assp", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/client", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": false, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets", 29 | "src/manifest.webmanifest" 30 | ], 31 | "styles": [ 32 | "src/styles.scss" 33 | ], 34 | "scripts": [] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "aot": true, 50 | "extractLicenses": true, 51 | "vendorChunk": false, 52 | "buildOptimizer": true, 53 | "budgets": [ 54 | { 55 | "type": "initial", 56 | "maximumWarning": "2mb", 57 | "maximumError": "5mb" 58 | }, 59 | { 60 | "type": "anyComponentStyle", 61 | "maximumWarning": "6kb", 62 | "maximumError": "10kb" 63 | } 64 | ], 65 | "serviceWorker": true, 66 | "ngswConfigPath": "ngsw-config.json" 67 | } 68 | } 69 | }, 70 | "serve": { 71 | "builder": "@angular-devkit/build-angular:dev-server", 72 | "options": { 73 | "browserTarget": "client:build" 74 | }, 75 | "configurations": { 76 | "production": { 77 | "browserTarget": "client:build:production" 78 | } 79 | } 80 | }, 81 | "extract-i18n": { 82 | "builder": "@angular-devkit/build-angular:extract-i18n", 83 | "options": { 84 | "browserTarget": "client:build" 85 | } 86 | }, 87 | "test": { 88 | "builder": "@angular-devkit/build-angular:karma", 89 | "options": { 90 | "main": "src/test.ts", 91 | "polyfills": "src/polyfills.ts", 92 | "tsConfig": "tsconfig.spec.json", 93 | "karmaConfig": "karma.conf.js", 94 | "assets": [ 95 | "src/favicon.ico", 96 | "src/assets", 97 | "src/manifest.webmanifest" 98 | ], 99 | "styles": [ 100 | "src/styles.scss" 101 | ], 102 | "scripts": [] 103 | } 104 | }, 105 | "lint": { 106 | "builder": "@angular-devkit/build-angular:tslint", 107 | "options": { 108 | "tsConfig": [ 109 | "tsconfig.app.json", 110 | "tsconfig.spec.json", 111 | "e2e/tsconfig.json" 112 | ], 113 | "exclude": [ 114 | "**/node_modules/**" 115 | ] 116 | } 117 | }, 118 | "e2e": { 119 | "builder": "@angular-devkit/build-angular:protractor", 120 | "options": { 121 | "protractorConfig": "e2e/protractor.conf.js", 122 | "devServerTarget": "client:serve" 123 | }, 124 | "configurations": { 125 | "production": { 126 | "devServerTarget": "client:serve:production" 127 | } 128 | } 129 | } 130 | } 131 | }}, 132 | "defaultProject": "client" 133 | } 134 | -------------------------------------------------------------------------------- /client/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /client/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: 'gcr.io/cloud-builders/npm:current' 3 | args: ['install'] 4 | - name: 'gcr.io/cloud-builders/npm:current' 5 | entrypoint: 'bash' 6 | args: 7 | - '-c' 8 | - | 9 | export APP_VERSION 10 | export NAMESPACE 11 | IFS=/ read -r APP_VERSION NAMESPACE <<< "$TAG_NAME" 12 | npm run build 13 | - name: 'gcr.io/cloud-builders/gsutil' 14 | entrypoint: 'bash' 15 | args: 16 | - '-c' 17 | - | 18 | export APP_VERSION 19 | export NAMESPACE 20 | IFS=/ read -r APP_VERSION NAMESPACE <<< "$TAG_NAME" 21 | gsutil mb -l $_REGION_NAME gs://client.example.com/ 22 | gsutil cp -r dist/client/* gs://client.example.com/ 23 | gsutil iam ch allUsers:objectViewer gs://client.example.com/ 24 | gsutil web set -m index.html -e 404.html gs://client.example.com/ 25 | -------------------------------------------------------------------------------- /client/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { ClientPage } from './app.po'; 2 | 3 | describe('client App', function() { 4 | let page: ClientPage; 5 | 6 | beforeEach(() => { 7 | page = new ClientPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /client/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class ClientPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | afterEach(async () => { 12 | // Assert that there are no errors emitted from the browser 13 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 14 | expect(logs).not.toContain(jasmine.objectContaining({ 15 | level: logging.Level.SEVERE, 16 | } as logging.Entry)); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('asp-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /client/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | <<<<<<< HEAD 16 | client: { 17 | clearContext: false // leave Jasmine Spec Runner output visible in browser 18 | ======= 19 | files: [ 20 | { pattern: './src/test.ts', watched: false } 21 | ], 22 | preprocessors: { 23 | './src/test.ts': ['angular-cli'] 24 | }, 25 | mime: { 26 | 'text/x-typescript': ['ts','tsx'] 27 | }, 28 | remapIstanbulReporter: { 29 | reports: { 30 | html: 'coverage', 31 | lcovonly: './coverage/coverage.lcov' 32 | } 33 | >>>>>>> b72a69f (Update dependencies (#20)) 34 | }, 35 | coverageIstanbulReporter: { 36 | dir: require('path').join(__dirname, './coverage/client'), 37 | reports: ['html', 'lcovonly', 'text-summary'], 38 | fixWebpackSourcePaths: true 39 | }, 40 | <<<<<<< HEAD 41 | reporters: ['progress', 'kjhtml'], 42 | ======= 43 | reporters: config.angularCli && config.angularCli.codeCoverage 44 | ? ['progress', 'karma-remap-istanbul'] 45 | : ['progress'], 46 | >>>>>>> b72a69f (Update dependencies (#20)) 47 | port: 9876, 48 | colors: true, 49 | logLevel: config.LOG_INFO, 50 | autoWatch: true, 51 | browsers: ['Chrome'], 52 | singleRun: false, 53 | restartOnFileChange: true 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /client/ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json", 3 | "index": "/index.html", 4 | "assetGroups": [ 5 | { 6 | "name": "app", 7 | "installMode": "prefetch", 8 | "resources": { 9 | "files": [ 10 | "/favicon.ico", 11 | "/index.html", 12 | "/manifest.webmanifest", 13 | "/*.css", 14 | "/*.js" 15 | ] 16 | } 17 | }, { 18 | "name": "assets", 19 | "installMode": "lazy", 20 | "updateMode": "prefetch", 21 | "resources": { 22 | "files": [ 23 | "/assets/**", 24 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" 25 | ] 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | <<<<<<< HEAD 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --open", 8 | "build": "ng build --aot --prod --service-worker", 9 | "test": "ng test --watch=false --code-coverage", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | ======= 13 | "license": "ISC", 14 | "angular-cli": {}, 15 | "scripts": { 16 | "ng": "ng", 17 | "start": "ng serve --proxy-config proxy.conf.json", 18 | "lint": "tslint \"src/**/*.ts\" --project src/tsconfig.json --type-check && tslint \"e2e/**/*.ts\" --project e2e/tsconfig.json --type-check", 19 | "test": "ng test --single-run --code-coverage", 20 | "pree2e": "webdriver-manager update --standalone false --gecko false", 21 | "e2e": "protractor", 22 | "sw": "sw-precache --root=dist --config=sw-precache-config.js" 23 | >>>>>>> b72a69f (Update dependencies (#20)) 24 | }, 25 | "private": false, 26 | "dependencies": { 27 | <<<<<<< HEAD 28 | "@angular/animations": "~8.2.9", 29 | "@angular/cdk": "^8.2.2", 30 | "@angular/common": "~8.2.9", 31 | "@angular/compiler": "~8.2.9", 32 | "@angular/core": "~8.2.9", 33 | "@angular/forms": "~8.2.9", 34 | "@angular/http": "^7.2.15", 35 | "@angular/material": "^8.2.2", 36 | "@angular/platform-browser": "~8.2.9", 37 | "@angular/platform-browser-dynamic": "~8.2.9", 38 | "@angular/pwa": "^0.803.8", 39 | "@angular/router": "~8.2.9", 40 | "@angular/service-worker": "~8.2.9", 41 | "@ngrx/effects": "^8.3.0", 42 | "@ngrx/entity": "^8.3.0", 43 | "@ngrx/store": "^8.3.0", 44 | "hammerjs": "^2.0.8", 45 | "rxjs": "~6.4.0", 46 | "tslib": "^1.10.0", 47 | "zone.js": "~0.9.1" 48 | }, 49 | "devDependencies": { 50 | "@angular-devkit/build-angular": "~0.803.7", 51 | "@angular/cli": "~8.3.7", 52 | "@angular/compiler-cli": "~8.2.9", 53 | "@angular/language-service": "~8.2.9", 54 | "@ngrx/schematics": "^8.3.0", 55 | "@ngrx/store-devtools": "^8.3.0", 56 | "@types/jasmine": "~3.3.8", 57 | "@types/jasminewd2": "~2.0.3", 58 | "@types/node": "~8.9.4", 59 | "codelyzer": "^5.0.0", 60 | "jasmine-core": "~3.4.0", 61 | "jasmine-spec-reporter": "~4.2.1", 62 | "karma": "~4.1.0", 63 | "karma-chrome-launcher": "~2.2.0", 64 | "karma-coverage-istanbul-reporter": "~2.0.1", 65 | "karma-jasmine": "~2.0.1", 66 | "karma-jasmine-html-reporter": "^1.4.0", 67 | "protractor": "~5.4.0", 68 | "ts-node": "~7.0.0", 69 | "tslint": "~5.15.0", 70 | "typescript": "~3.5.3" 71 | ======= 72 | "@angular/common": "^2.3.1", 73 | "@angular/compiler": "^2.3.1", 74 | "@angular/core": "^2.3.1", 75 | "@angular/forms": "^2.3.1", 76 | "@angular/http": "^2.3.1", 77 | "@angular/material": "^2.0.0-beta.1", 78 | "@angular/platform-browser": "^2.3.1", 79 | "@angular/platform-browser-dynamic": "^2.3.1", 80 | "@angular/router": "^3.3.1", 81 | "@ngrx/core": "^1.2.0", 82 | "@ngrx/effects": "^2.0.0", 83 | "@ngrx/store": "^2.2.1", 84 | "core-js": "^2.4.1", 85 | "hammerjs": "^2.0.8", 86 | "reselect": "^2.5.4", 87 | "rxjs": "^5.0.1", 88 | "ts-helpers": "^1.1.1", 89 | "zone.js": "^0.7.2" 90 | }, 91 | "devDependencies": { 92 | "@angular/compiler-cli": "^2.3.1", 93 | "@ngrx/store-devtools": "^3.2.3", 94 | "@types/hammerjs": "^2.0.34", 95 | "@types/jasmine": "2.5.38", 96 | "@types/node": "^6.0.42", 97 | "angular-cli": "1.0.0-beta.28.3", 98 | "codelyzer": "~2.0.0-beta.1", 99 | "jasmine-core": "2.5.2", 100 | "jasmine-spec-reporter": "2.5.0", 101 | "karma": "1.2.0", 102 | "karma-chrome-launcher": "^2.0.0", 103 | "karma-cli": "^1.0.1", 104 | "karma-jasmine": "^1.0.2", 105 | "karma-remap-istanbul": "^0.2.1", 106 | "ngrx-store-freeze": "^0.1.6", 107 | "protractor": "~4.0.13", 108 | "sw-precache": "^4.3.0", 109 | "ts-node": "1.2.1", 110 | "tslint": "^4.3.0", 111 | "typescript": "~2.0.3" 112 | >>>>>>> b72a69f (Update dependencies (#20)) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /client/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:3000", 4 | "secure": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /client/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { path: '', loadChildren: './modules/users/users.module#UsersModule' }, 6 | ]; 7 | 8 | @NgModule({ 9 | imports: [RouterModule.forRoot(routes)], 10 | exports: [RouterModule] 11 | }) 12 | export class AppRoutingModule { } 13 | -------------------------------------------------------------------------------- /client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | <<<<<<< HEAD 7 | beforeEach(async(() => { 8 | ======= 9 | beforeEach(() => { 10 | >>>>>>> b72a69f (Update dependencies (#20)) 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | RouterTestingModule 14 | ], 15 | declarations: [ 16 | AppComponent 17 | ], 18 | <<<<<<< HEAD 19 | }).compileComponents(); 20 | })); 21 | 22 | it('should create the app', () => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.debugElement.componentInstance; 25 | expect(app).toBeTruthy(); 26 | }); 27 | ======= 28 | }); 29 | TestBed.compileComponents(); 30 | }); 31 | 32 | it('should create the app', async(() => { 33 | const fixture = TestBed.createComponent(AppComponent); 34 | const app = fixture.debugElement.componentInstance; 35 | expect(app).toBeTruthy(); 36 | })); 37 | 38 | it(`should have as title 'app works!'`, async(() => { 39 | const fixture = TestBed.createComponent(AppComponent); 40 | const app = fixture.debugElement.componentInstance; 41 | expect(app.title).toEqual('app works!'); 42 | })); 43 | 44 | it('should render title in a h1 tag', async(() => { 45 | const fixture = TestBed.createComponent(AppComponent); 46 | fixture.detectChanges(); 47 | const compiled = fixture.debugElement.nativeElement; 48 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 49 | })); 50 | >>>>>>> b72a69f (Update dependencies (#20)) 51 | }); 52 | -------------------------------------------------------------------------------- /client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'assp-root', 5 | template: ` 6 | 7 | `, 8 | styles: [``] 9 | }) 10 | export class AppComponent { 11 | } 12 | -------------------------------------------------------------------------------- /client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | 7 | import { StoreModule } from '@ngrx/store'; 8 | import { reducers, metaReducers } from './reducers'; 9 | import { EffectsModule } from '@ngrx/effects'; 10 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 11 | 12 | import { MaterialModule } from './modules/material/material.module'; 13 | 14 | import { AppRoutingModule } from './app-routing.module'; 15 | import { AppComponent } from './app.component'; 16 | 17 | import { environment } from '../environments/environment'; 18 | import { ServiceWorkerModule } from '@angular/service-worker'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | AppComponent, 23 | ], 24 | imports: [ 25 | BrowserModule, 26 | AppRoutingModule, 27 | HttpClientModule, 28 | BrowserAnimationsModule, 29 | MaterialModule, 30 | StoreModule.forRoot(reducers, { metaReducers }), 31 | StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production }), 32 | EffectsModule.forRoot([]), 33 | ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), 34 | ], 35 | providers: [], 36 | bootstrap: [AppComponent] 37 | }) 38 | export class AppModule { } 39 | -------------------------------------------------------------------------------- /client/src/app/modules/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LayoutModule } from '@angular/cdk/layout'; 4 | import { 5 | MatCardModule, 6 | MatProgressSpinnerModule, 7 | } from '@angular/material'; 8 | 9 | @NgModule({ 10 | declarations: [], 11 | imports: [ 12 | CommonModule, 13 | LayoutModule, 14 | MatCardModule, 15 | MatProgressSpinnerModule, 16 | ], 17 | exports: [ 18 | LayoutModule, 19 | MatCardModule, 20 | MatProgressSpinnerModule, 21 | ], 22 | }) 23 | export class MaterialModule { } 24 | -------------------------------------------------------------------------------- /client/src/app/modules/users/actions/user.actions.ts: -------------------------------------------------------------------------------- 1 | import { createAction, props } from '@ngrx/store'; 2 | 3 | import { UsersResponse, User } from '../models/user.model'; 4 | 5 | export const fetchUsers = createAction( 6 | '[Users Page] Fetch Users', 7 | ); 8 | 9 | export const fetchUsersSuccess = createAction( 10 | '[Users Page] Fetch Users Success', 11 | props<{ payload: UsersResponse }>(), 12 | ); 13 | 14 | export const fetchUsersFailed = createAction( 15 | '[Users Page] Fetch Users Failed', 16 | props<{ error: any }>(), 17 | ); 18 | -------------------------------------------------------------------------------- /client/src/app/modules/users/components/users-list/users-list.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 | 8 | 9 | {{ user.name }} 10 | 11 | 12 |

Age: {{ user.age }}

13 |

Email: {{ user.email }}

14 | 15 | 16 |

17 | -------------------------------------------------------------------------------- /client/src/app/modules/users/components/users-list/users-list.component.scss: -------------------------------------------------------------------------------- 1 | mat-card { 2 | display: inline-block; 3 | margin: 1%; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/app/modules/users/components/users-list/users-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 3 | import { StoreModule } from '@ngrx/store'; 4 | 5 | import { UsersListComponent } from './users-list.component'; 6 | import { reducers } from '../../reducers'; 7 | 8 | describe('UsersListComponent', () => { 9 | let component: UsersListComponent; 10 | let fixture: ComponentFixture; 11 | 12 | 13 | beforeEach(async(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [ UsersListComponent ], 16 | imports: [ StoreModule.forRoot(reducers) ], 17 | schemas: [ CUSTOM_ELEMENTS_SCHEMA ], 18 | }) 19 | .compileComponents(); 20 | })); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(UsersListComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /client/src/app/modules/users/components/users-list/users-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | import { Store, select } from '@ngrx/store'; 3 | import { Component, OnInit, OnDestroy } from '@angular/core'; 4 | 5 | import * as UserActions from '../../actions/user.actions'; 6 | import { User } from '../../models/user.model'; 7 | import * as fromUsers from '../../reducers'; 8 | 9 | @Component({ 10 | selector: 'assp-users-list', 11 | templateUrl: './users-list.component.html', 12 | styleUrls: ['./users-list.component.scss'] 13 | }) 14 | export class UsersListComponent implements OnInit, OnDestroy { 15 | showSpinner = true; 16 | allUsers: Array; 17 | usersState$; 18 | 19 | constructor( 20 | private store: Store, 21 | ) { } 22 | 23 | ngOnInit() { 24 | this.store.dispatch(UserActions.fetchUsers()); 25 | 26 | this.usersState$ = this.store.pipe(select(fromUsers.getUsers)) 27 | .subscribe((state: any) => { 28 | this.allUsers = state.users; 29 | 30 | // Stop spinner if there is any data. 31 | if (this.allUsers && this.allUsers.length >= 1) { 32 | this.showSpinner = false; 33 | } 34 | }); 35 | } 36 | 37 | ngOnDestroy() { 38 | this.usersState$.unsubscribe(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /client/src/app/modules/users/effects/user.effects.ts: -------------------------------------------------------------------------------- 1 | import { of } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | import { map, catchError, exhaustMap, tap } from 'rxjs/operators'; 4 | import { Actions, Effect, ofType, createEffect } from '@ngrx/effects'; 5 | 6 | import { 7 | fetchUsers, fetchUsersSuccess, fetchUsersFailed, 8 | } from '../actions/user.actions'; 9 | import { UserService } from '../services/user.service'; 10 | 11 | 12 | @Injectable() 13 | export class UserEffects { 14 | 15 | loadAllusers$ = createEffect(() => 16 | this.actions$.pipe( 17 | ofType(fetchUsers), 18 | exhaustMap(action => 19 | this.userService.getUsers().pipe( 20 | map(data => fetchUsersSuccess({payload: data})), 21 | catchError(error => of(fetchUsersFailed({error}))) 22 | ) 23 | ) 24 | ) 25 | ); 26 | 27 | constructor( 28 | private actions$: Actions, 29 | private userService: UserService, 30 | ) {} 31 | 32 | } 33 | -------------------------------------------------------------------------------- /client/src/app/modules/users/models/user.model.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | _id?: number; 3 | name: string; 4 | age: string; 5 | email: string; 6 | date_joined: number; 7 | dirty_fields: Array; 8 | } 9 | 10 | export interface UsersResponse { 11 | _message: string; 12 | users: User[]; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/app/modules/users/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionReducer, 3 | ActionReducerMap, 4 | createFeatureSelector, 5 | createSelector, 6 | MetaReducer, 7 | } from '@ngrx/store'; 8 | import { environment } from '../../../../environments/environment'; 9 | import * as fromUser from './user.reducer'; 10 | 11 | export interface State { 12 | users: fromUser.State; 13 | } 14 | 15 | export const reducers: ActionReducerMap = { 16 | users: fromUser.reducer, 17 | }; 18 | 19 | 20 | export const metaReducers: MetaReducer[] = !environment.production ? [] : []; 21 | 22 | /** 23 | * Layout Reducers 24 | */ 25 | export const getUsrsState = createFeatureSelector('users'); 26 | 27 | export const getUsers = createSelector( 28 | getUsrsState, 29 | (state: fromUser.State) => state.users 30 | ); 31 | -------------------------------------------------------------------------------- /client/src/app/modules/users/reducers/user.reducer.ts: -------------------------------------------------------------------------------- 1 | import { Action, createReducer, on } from '@ngrx/store'; 2 | 3 | import { UsersResponse } from '../models/user.model'; 4 | import * as UserActions from '../actions/user.actions'; 5 | 6 | 7 | export type State = UsersResponse; 8 | 9 | export const initialState: State = { 10 | _message: '', 11 | users: [{ 12 | _id: 1, 13 | name: '', 14 | age: '', 15 | email: '', 16 | date_joined: 12, 17 | dirty_fields: [], 18 | }] 19 | }; 20 | 21 | const userReducer = createReducer( 22 | initialState, 23 | on(UserActions.fetchUsers, (state, action) => ({ ...state })), 24 | on(UserActions.fetchUsersSuccess, (state, action) => action.payload ), 25 | ); 26 | 27 | export function reducer(state: State | undefined, action: Action) { 28 | return userReducer(state, action); 29 | } 30 | -------------------------------------------------------------------------------- /client/src/app/modules/users/services/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, getTestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; 3 | 4 | import { UserService } from './user.service'; 5 | 6 | describe('UserService', () => { 7 | let injector: TestBed; 8 | let service: UserService; 9 | let httpMock: HttpTestingController; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | HttpClientTestingModule, 15 | ], 16 | providers: [ UserService ], 17 | }); 18 | 19 | injector = getTestBed(); 20 | service = injector.get(UserService); 21 | httpMock = injector.get(HttpTestingController); 22 | }); 23 | 24 | afterEach(() => { 25 | httpMock.verify(); 26 | }); 27 | 28 | it('should be created', () => { 29 | expect(service).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /client/src/app/modules/users/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { Injectable } from '@angular/core'; 3 | import { HttpClient } from '@angular/common/http'; 4 | 5 | import { User, UsersResponse } from '../models/user.model'; 6 | import { environment } from '../../../../environments/environment'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class UserService { 12 | 13 | constructor( 14 | private http: HttpClient, 15 | ) { } 16 | 17 | getUsers(): Observable { 18 | return this.http.get(`${environment.backend}/users`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/app/modules/users/users-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { UsersListComponent } from './components/users-list/users-list.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: UsersListComponent, 10 | }, 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class UsersRoutingModule { } 18 | -------------------------------------------------------------------------------- /client/src/app/modules/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { StoreModule } from '@ngrx/store'; 5 | import { EffectsModule } from '@ngrx/effects'; 6 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 7 | 8 | import { UsersRoutingModule } from './users-routing.module'; 9 | import { reducers, metaReducers } from './reducers'; 10 | 11 | import { MaterialModule } from '../material/material.module'; 12 | 13 | import { UsersListComponent } from './components/users-list/users-list.component'; 14 | 15 | import { UserService } from './services/user.service'; 16 | 17 | import { UserEffects } from './effects/user.effects'; 18 | 19 | import { environment } from '../../../environments/environment'; 20 | 21 | 22 | @NgModule({ 23 | declarations: [ 24 | UsersListComponent, 25 | ], 26 | imports: [ 27 | CommonModule, 28 | UsersRoutingModule, 29 | MaterialModule, 30 | StoreModule.forFeature('users', reducers, { metaReducers }), 31 | StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production }), 32 | EffectsModule.forFeature([UserEffects]), 33 | ], 34 | providers: [ 35 | UserService, 36 | ], 37 | }) 38 | export class UsersModule { } 39 | -------------------------------------------------------------------------------- /client/src/app/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ActionReducer, 3 | ActionReducerMap, 4 | createFeatureSelector, 5 | createSelector, 6 | MetaReducer 7 | } from '@ngrx/store'; 8 | import { environment } from '../../environments/environment'; 9 | 10 | export interface State { 11 | } 12 | 13 | export const reducers: ActionReducerMap = { 14 | }; 15 | 16 | 17 | export const metaReducers: MetaReducer[] = !environment.production ? [] : []; 18 | -------------------------------------------------------------------------------- /client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/.gitkeep -------------------------------------------------------------------------------- /client/src/assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-144x144.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-152x152.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-192x192.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-384x384.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-72x72.png -------------------------------------------------------------------------------- /client/src/assets/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/assets/icons/icon-96x96.png -------------------------------------------------------------------------------- /client/src/assp-theme.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/theming'; 2 | // Plus imports for other components in your app. 3 | 4 | // Include the common styles for Angular Material. We include this here so that you only 5 | // have to load a single css file for Angular Material in your app. 6 | // Be sure that you only ever include this mixin once! 7 | @include mat-core(); 8 | 9 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 10 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 11 | // hue. Available color palettes: https://material.io/design/color/ 12 | $assp-app-primary: mat-palette($mat-light-blue); 13 | $assp-app-accent: mat-palette($mat-pink, A200, A100, A400); 14 | 15 | // The warn palette is optional (defaults to red). 16 | $assp-app-warn: mat-palette($mat-red); 17 | 18 | $primary: mat-color($assp-app-primary); 19 | $accent: mat-color($assp-app-accent); 20 | $warn: mat-color($assp-app-warn); 21 | $textColor: #ddd; 22 | 23 | // Create the theme object (a Sass map containing all of the palettes). 24 | $assp-app-theme: mat-dark-theme($assp-app-primary, $assp-app-accent, $assp-app-warn); 25 | 26 | // Include theme styles for core and each component used in your app. 27 | // Alternatively, you can import and @include the theme mixins for each component 28 | // that you are using. 29 | @include angular-material-theme($assp-app-theme); 30 | -------------------------------------------------------------------------------- /client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | backend: '', 4 | }; 5 | -------------------------------------------------------------------------------- /client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | backend: 'http://localhost:8000', 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/favicon.ico -------------------------------------------------------------------------------- /client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client 6 | 7 | 8 | 9 | <<<<<<< HEAD 10 | 11 | 12 | 13 | ======= 14 | >>>>>>> b72a69f (Update dependencies (#20)) 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/src/main.ts: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | import 'hammerjs'; 3 | 4 | ======= 5 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 6 | >>>>>>> b72a69f (Update dependencies (#20)) 7 | import { enableProdMode } from '@angular/core'; 8 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 9 | 10 | import { AppModule } from './app/app.module'; 11 | import { environment } from './environments/environment'; 12 | <<<<<<< HEAD 13 | ======= 14 | import { AppModule } from './app/app.module'; 15 | >>>>>>> b72a69f (Update dependencies (#20)) 16 | 17 | if (environment.production) { 18 | enableProdMode(); 19 | } 20 | 21 | platformBrowserDynamic().bootstrapModule(AppModule) 22 | .catch(err => console.error(err)); 23 | -------------------------------------------------------------------------------- /client/src/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "short_name": "client", 4 | "theme_color": "#1976d2", 5 | "background_color": "#fafafa", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-96x96.png", 17 | "sizes": "96x96", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-128x128.png", 22 | "sizes": "128x128", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-144x144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-152x152.png", 32 | "sizes": "152x152", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "assets/icons/icon-192x192.png", 37 | "sizes": "192x192", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "assets/icons/icon-384x384.png", 42 | "sizes": "384x384", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "assets/icons/icon-512x512.png", 47 | "sizes": "512x512", 48 | "type": "image/png" 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | /** 3 | * This file includes polyfills needed by Angular and is loaded before the app. 4 | * You can add your own extra polyfills to this file. 5 | * 6 | * This file is divided into 2 sections: 7 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 8 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 9 | * file. 10 | * 11 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 12 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 13 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 14 | * 15 | * Learn more in https://angular.io/guide/browser-support 16 | */ 17 | 18 | /*************************************************************************************************** 19 | * BROWSER POLYFILLS 20 | */ 21 | 22 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 23 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 24 | 25 | /** 26 | * Web Animations `@angular/platform-browser/animations` 27 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 28 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 29 | */ 30 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 31 | 32 | /** 33 | * By default, zone.js will patch all possible macroTask and DomEvents 34 | * user can disable parts of macroTask/DomEvents patch by setting following flags 35 | * because those flags need to be set before `zone.js` being loaded, and webpack 36 | * will put import in the top of bundle, so user need to create a separate file 37 | * in this directory (for example: zone-flags.ts), and put the following flags 38 | * into that file, and then add the following code before importing zone.js. 39 | * import './zone-flags.ts'; 40 | * 41 | * The flags allowed in zone-flags.ts are listed here. 42 | * 43 | * The following flags will work for all browsers. 44 | * 45 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 46 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 47 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 48 | * 49 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 50 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 51 | * 52 | * (window as any).__Zone_enable_cross_context_check = true; 53 | * 54 | */ 55 | 56 | /*************************************************************************************************** 57 | * Zone JS is required by default for Angular itself. 58 | */ 59 | import 'zone.js/dist/zone'; // Included with Angular CLI. 60 | 61 | 62 | /*************************************************************************************************** 63 | * APPLICATION IMPORTS 64 | */ 65 | ======= 66 | // This file includes polyfills needed by Angular and is loaded before the app. 67 | // You can add your own extra polyfills to this file. 68 | import 'core-js/es6/symbol'; 69 | import 'core-js/es6/object'; 70 | import 'core-js/es6/function'; 71 | import 'core-js/es6/parse-int'; 72 | import 'core-js/es6/parse-float'; 73 | import 'core-js/es6/number'; 74 | import 'core-js/es6/math'; 75 | import 'core-js/es6/string'; 76 | import 'core-js/es6/date'; 77 | import 'core-js/es6/array'; 78 | import 'core-js/es6/regexp'; 79 | import 'core-js/es6/map'; 80 | import 'core-js/es6/set'; 81 | import 'core-js/es6/reflect'; 82 | 83 | import 'core-js/es7/reflect'; 84 | import 'zone.js/dist/zone'; 85 | 86 | // If you need to support the browsers/features below, uncomment the import 87 | // and run `npm install import-name-here'; 88 | // Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 89 | 90 | // Needed for: IE9 91 | // import 'classlist.js'; 92 | 93 | // Animations 94 | // Needed for: All but Chrome and Firefox, Not supported in IE9 95 | // import 'web-animations-js'; 96 | 97 | // Date, currency, decimal and percent pipes 98 | // Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 99 | // import 'intl'; 100 | 101 | // NgClass on SVG elements 102 | // Needed for: IE10, IE11 103 | // import 'classlist.js'; 104 | >>>>>>> b72a69f (Update dependencies (#20)) 105 | -------------------------------------------------------------------------------- /client/src/service-worker.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/client/src/service-worker.js -------------------------------------------------------------------------------- /client/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | <<<<<<< HEAD 4 | import 'zone.js/dist/zone-testing'; 5 | ======= 6 | import 'zone.js/dist/long-stack-trace-zone'; 7 | import 'zone.js/dist/proxy.js'; 8 | import 'zone.js/dist/sync-test'; 9 | import 'zone.js/dist/jasmine-patch'; 10 | import 'zone.js/dist/async-test'; 11 | import 'zone.js/dist/fake-async-test'; 12 | >>>>>>> b72a69f (Update dependencies (#20)) 13 | import { getTestBed } from '@angular/core/testing'; 14 | import { 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting 17 | } from '@angular/platform-browser-dynamic/testing'; 18 | 19 | declare const require: any; 20 | 21 | <<<<<<< HEAD 22 | ======= 23 | // Prevent Karma from running prematurely. 24 | __karma__.loaded = function () {}; 25 | 26 | >>>>>>> b72a69f (Update dependencies (#20)) 27 | // First, initialize the Angular testing environment. 28 | getTestBed().initTestEnvironment( 29 | BrowserDynamicTestingModule, 30 | platformBrowserDynamicTesting() 31 | ); 32 | // Then we find all the tests. 33 | const context = require.context('./', true, /\.spec\.ts$/); 34 | // And load the modules. 35 | context.keys().map(context); 36 | <<<<<<< HEAD 37 | ======= 38 | // Finally, start Karma to run the tests. 39 | __karma__.start(); 40 | >>>>>>> b72a69f (Update dependencies (#20)) 41 | -------------------------------------------------------------------------------- /client/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": ["es6", "dom"], 8 | "mapRoot": "./", 9 | "module": "es6", 10 | "moduleResolution": "node", 11 | "outDir": "../dist/out-tsc", 12 | "sourceMap": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "../node_modules/@types" 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/sw-precache-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | navigateFallback: '/index.html', 3 | stripPrefix: 'dist', 4 | root: 'dist/', 5 | staticFileGlobs: [ 6 | 'dist/index.html', 7 | 'dist/**.js', 8 | 'dist/**.css', 9 | 'dist/*/**.jpg' 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | <<<<<<< HEAD 5 | "array-type": false, 6 | "arrow-parens": false, 7 | "deprecation": { 8 | "severity": "warning" 9 | }, 10 | "component-class-suffix": true, 11 | "contextual-lifecycle": true, 12 | "directive-class-suffix": true, 13 | "directive-selector": [ 14 | true, 15 | "attribute", 16 | "assp", 17 | "camelCase" 18 | ], 19 | "component-selector": [ 20 | ======= 21 | "callable-types": true, 22 | "class-name": true, 23 | "comment-format": [ 24 | >>>>>>> b72a69f (Update dependencies (#20)) 25 | true, 26 | "element", 27 | "assp", 28 | "kebab-case" 29 | ], 30 | <<<<<<< HEAD 31 | "import-blacklist": [ 32 | ======= 33 | "curly": true, 34 | "eofline": true, 35 | "forin": true, 36 | "import-blacklist": [true, "rxjs"], 37 | "import-spacing": true, 38 | "indent": [ 39 | >>>>>>> b72a69f (Update dependencies (#20)) 40 | true, 41 | "rxjs/Rx" 42 | ], 43 | <<<<<<< HEAD 44 | "interface-name": false, 45 | "max-classes-per-file": false, 46 | ======= 47 | "interface-over-type-literal": true, 48 | "label-position": true, 49 | >>>>>>> b72a69f (Update dependencies (#20)) 50 | "max-line-length": [ 51 | true, 52 | 140 53 | ], 54 | "member-access": false, 55 | "member-ordering": [ 56 | true, 57 | { 58 | "order": [ 59 | "static-field", 60 | "instance-field", 61 | "static-method", 62 | "instance-method" 63 | ] 64 | } 65 | ], 66 | "no-consecutive-blank-lines": false, 67 | "no-console": [ 68 | true, 69 | "debug", 70 | "info", 71 | "time", 72 | "timeEnd", 73 | "trace" 74 | ], 75 | <<<<<<< HEAD 76 | "no-empty": false, 77 | "no-inferrable-types": [ 78 | true, 79 | "ignore-params" 80 | ], 81 | "no-non-null-assertion": true, 82 | "no-redundant-jsdoc": true, 83 | "no-switch-case-fall-through": true, 84 | "no-var-requires": false, 85 | "object-literal-key-quotes": [ 86 | ======= 87 | "no-construct": true, 88 | "no-debugger": true, 89 | "no-duplicate-variable": true, 90 | "no-empty": false, 91 | "no-empty-interface": true, 92 | "no-eval": true, 93 | "no-inferrable-types": true, 94 | "no-shadowed-variable": true, 95 | "no-string-literal": false, 96 | "no-string-throw": true, 97 | "no-switch-case-fall-through": true, 98 | "no-trailing-whitespace": true, 99 | "no-unused-expression": true, 100 | "no-use-before-declare": true, 101 | "no-var-keyword": true, 102 | "object-literal-sort-keys": false, 103 | "one-line": [ 104 | >>>>>>> b72a69f (Update dependencies (#20)) 105 | true, 106 | "as-needed" 107 | ], 108 | <<<<<<< HEAD 109 | "object-literal-sort-keys": false, 110 | "ordered-imports": false, 111 | ======= 112 | "prefer-const": true, 113 | >>>>>>> b72a69f (Update dependencies (#20)) 114 | "quotemark": [ 115 | true, 116 | "single" 117 | ], 118 | <<<<<<< HEAD 119 | "trailing-comma": false, 120 | "no-conflicting-lifecycle": true, 121 | "no-empty-interface": false, 122 | "no-host-metadata-property": true, 123 | ======= 124 | "radix": true, 125 | "semicolon": [ 126 | "always" 127 | ], 128 | "triple-equals": [ 129 | true, 130 | "allow-null-check" 131 | ], 132 | "typedef-whitespace": [ 133 | true, 134 | { 135 | "call-signature": "nospace", 136 | "index-signature": "nospace", 137 | "parameter": "nospace", 138 | "property-declaration": "nospace", 139 | "variable-declaration": "nospace" 140 | } 141 | ], 142 | "typeof-compare": true, 143 | "unified-signatures": true, 144 | "variable-name": false, 145 | "whitespace": [ 146 | true, 147 | "check-branch", 148 | "check-decl", 149 | "check-operator", 150 | "check-separator", 151 | "check-type" 152 | ], 153 | 154 | "directive-selector": [true, "attribute", "app", "camelCase"], 155 | "component-selector": [true, "element", "app", "kebab-case"], 156 | "use-input-property-decorator": true, 157 | "use-output-property-decorator": true, 158 | "use-host-property-decorator": true, 159 | >>>>>>> b72a69f (Update dependencies (#20)) 160 | "no-input-rename": true, 161 | "no-inputs-metadata-property": true, 162 | "no-output-native": true, 163 | "no-output-on-prefix": true, 164 | "no-output-rename": true, 165 | <<<<<<< HEAD 166 | "no-outputs-metadata-property": true, 167 | "template-banana-in-box": true, 168 | "template-no-negated-async": true, 169 | "use-lifecycle-interface": true, 170 | "use-pipe-transform-interface": true 171 | }, 172 | "rulesDirectory": [ 173 | "codelyzer" 174 | ] 175 | ======= 176 | "use-life-cycle-interface": true, 177 | "use-pipe-transform-interface": true, 178 | "component-class-suffix": true, 179 | "directive-class-suffix": true, 180 | "no-access-missing-member": true, 181 | "templates-use-public": true, 182 | "invoke-injectable": true 183 | } 184 | >>>>>>> b72a69f (Update dependencies (#20)) 185 | } 186 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: true 4 | comment: 5 | behavior: default 6 | layout: header, diff 7 | require_changes: false 8 | coverage: 9 | precision: 2 10 | range: 11 | - 70.0 12 | - 100.0 13 | round: down 14 | status: 15 | changes: false 16 | project: 17 | default: 18 | enabled: yes 19 | target: 85% 20 | patch: 21 | default: 22 | enabled: yes 23 | <<<<<<< HEAD 24 | target: 50% 25 | ======= 26 | target: 80% 27 | >>>>>>> b72a69f (Update dependencies (#20)) 28 | parsers: 29 | gcov: 30 | branch_detection: 31 | conditional: true 32 | loop: true 33 | macro: false 34 | method: false 35 | javascript: 36 | enable_partials: false 37 | ignore: 38 | - "server/manage.py" 39 | - "server/config" 40 | <<<<<<< HEAD 41 | - "server/locustfile" 42 | ======= 43 | >>>>>>> b72a69f (Update dependencies (#20)) 44 | - "server/**/apps.py" 45 | -------------------------------------------------------------------------------- /database/autopgsqlbackup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | #===================================================================== 4 | # Set the following variables to your system needs 5 | # (Detailed instructions below variables) 6 | #===================================================================== 7 | 8 | # Username to access the PostgreSQL server e.g. dbuser 9 | USERNAME=$POSTGRES_USER 10 | 11 | # Password 12 | # create a file $HOME/.pgpass containing a line like this 13 | # hostname:*:*:dbuser:dbpass 14 | # replace hostname with the value of DBHOST and postgres with 15 | # the value of USERNAME 16 | 17 | # Host name (or IP address) of PostgreSQL server e.g localhost 18 | DBHOST=localhost 19 | 20 | # List of DBNAMES for Daily/Weekly Backup e.g. "DB1 DB2 DB3" 21 | DBNAMES="$POSTGRES_DB" 22 | 23 | # Backup directory location e.g /backups 24 | BACKUPDIR="/var/backups/postgres" 25 | 26 | # Mail setup 27 | # What would you like to be mailed to you? 28 | # - log : send only log file 29 | # - files : send log file and sql files as attachments (see docs) 30 | # - stdout : will simply output the log to the screen if run manually. 31 | MAILCONTENT="log" 32 | 33 | # Set the maximum allowed email size in k. (4000 = approx 5MB email [see docs]) 34 | MAXATTSIZE="4000" 35 | 36 | # Email Address to send mail to? (user@domain.com) 37 | MAILADDR="root@localhost" 38 | 39 | 40 | # ============================================================ 41 | # === ADVANCED OPTIONS ( Read the doc's below for details )=== 42 | #============================================================= 43 | 44 | # List of DBBNAMES for Monthly Backups. 45 | MDBNAMES="template1 $DBNAMES" 46 | 47 | # List of DBNAMES to EXLUCDE if DBNAMES are set to all (must be in " quotes) 48 | DBEXCLUDE="" 49 | 50 | # Include CREATE DATABASE in backup? 51 | CREATE_DATABASE=yes 52 | 53 | # Separate backup directory and file for each DB? (yes or no) 54 | SEPDIR=yes 55 | 56 | # Which day do you want weekly backups? (1 to 7 where 1 is Monday) 57 | DOWEEKLY=6 58 | 59 | # Choose Compression type. (gzip or bzip2) 60 | COMP=bzip2 61 | 62 | # Command to run before backups (uncomment to use) 63 | #PREBACKUP="/etc/pgsql-backup-pre" 64 | 65 | # Command run after backups (uncomment to use) 66 | #POSTBACKUP="bash /home/backups/scripts/ftp_pgsql" 67 | 68 | #===================================================================== 69 | # Options documentation 70 | #===================================================================== 71 | # Set USERNAME and PASSWORD of a user that has at least SELECT permission 72 | # to ALL databases. 73 | # 74 | # Set the DBHOST option to the server you wish to backup, leave the 75 | # default to backup "this server".(to backup multiple servers make 76 | # copies of this file and set the options for that server) 77 | # 78 | # Put in the list of DBNAMES(Databases)to be backed up. If you would like 79 | # to backup ALL DBs on the server set DBNAMES="all".(if set to "all" then 80 | # any new DBs will automatically be backed up without needing to modify 81 | # this backup script when a new DB is created). 82 | # 83 | # If the DB you want to backup has a space in the name replace the space 84 | # with a % e.g. "data base" will become "data%base" 85 | # NOTE: Spaces in DB names may not work correctly when SEPDIR=no. 86 | # 87 | # You can change the backup storage location from /backups to anything 88 | # you like by using the BACKUPDIR setting.. 89 | # 90 | # The MAILCONTENT and MAILADDR options and pretty self explanitory, use 91 | # these to have the backup log mailed to you at any email address or multiple 92 | # email addresses in a space seperated list. 93 | # (If you set mail content to "log" you will require access to the "mail" program 94 | # on your server. If you set this to "files" you will have to have mutt installed 95 | # on your server. If you set it sto stdout it will log to the screen if run from 96 | # the console or to the cron job owner if run through cron) 97 | # 98 | # MAXATTSIZE sets the largest allowed email attachments total (all backup files) you 99 | # want the script to send. This is the size before it is encoded to be sent as an email 100 | # so if your mail server will allow a maximum mail size of 5MB I would suggest setting 101 | # MAXATTSIZE to be 25% smaller than that so a setting of 4000 would probably be fine. 102 | # 103 | # Finally copy automysqlbackup.sh to anywhere on your server and make sure 104 | # to set executable permission. You can also copy the script to 105 | # /etc/cron.daily to have it execute automatically every night or simply 106 | # place a symlink in /etc/cron.daily to the file if you wish to keep it 107 | # somwhere else. 108 | # NOTE:On Debian copy the file with no extention for it to be run 109 | # by cron e.g just name the file "automysqlbackup" 110 | # 111 | # Thats it.. 112 | # 113 | # 114 | # === Advanced options doc's === 115 | # 116 | # The list of MDBNAMES is the DB's to be backed up only monthly. You should 117 | # always include "mysql" in this list to backup your user/password 118 | # information along with any other DBs that you only feel need to 119 | # be backed up monthly. (if using a hosted server then you should 120 | # probably remove "mysql" as your provider will be backing this up) 121 | # NOTE: If DBNAMES="all" then MDBNAMES has no effect as all DBs will be backed 122 | # up anyway. 123 | # 124 | # If you set DBNAMES="all" you can configure the option DBEXCLUDE. Other 125 | # wise this option will not be used. 126 | # This option can be used if you want to backup all dbs, but you want 127 | # exclude some of them. (eg. a db is to big). 128 | # 129 | # Set CREATE_DATABASE to "yes" (the default) if you want your SQL-Dump to create 130 | # a database with the same name as the original database when restoring. 131 | # Saying "no" here will allow your to specify the database name you want to 132 | # restore your dump into, making a copy of the database by using the dump 133 | # created with automysqlbackup. 134 | # NOTE: Not used if SEPDIR=no 135 | # 136 | # The SEPDIR option allows you to choose to have all DBs backed up to 137 | # a single file (fast restore of entire server in case of crash) or to 138 | # seperate directories for each DB (each DB can be restored seperately 139 | # in case of single DB corruption or loss). 140 | # 141 | # To set the day of the week that you would like the weekly backup to happen 142 | # set the DOWEEKLY setting, this can be a value from 1 to 7 where 1 is Monday, 143 | # The default is 6 which means that weekly backups are done on a Saturday. 144 | # 145 | # COMP is used to choose the copmression used, options are gzip or bzip2. 146 | # bzip2 will produce slightly smaller files but is more processor intensive so 147 | # may take longer to complete. 148 | # 149 | # Use PREBACKUP and POSTBACKUP to specify Per and Post backup commands 150 | # or scripts to perform tasks either before or after the backup process. 151 | # 152 | # 153 | #===================================================================== 154 | # Backup Rotation.. 155 | #===================================================================== 156 | # 157 | # Daily Backups are rotated weekly.. 158 | # Weekly Backups are run by default on Saturday Morning when 159 | # cron.daily scripts are run...Can be changed with DOWEEKLY setting.. 160 | # Weekly Backups are rotated on a 5 week cycle.. 161 | # Monthly Backups are run on the 1st of the month.. 162 | # Monthly Backups are NOT rotated automatically... 163 | # It may be a good idea to copy Monthly backups offline or to another 164 | # server.. 165 | # 166 | #===================================================================== 167 | # Please Note!! 168 | #===================================================================== 169 | # 170 | # I take no resposibility for any data loss or corruption when using 171 | # this script.. 172 | # This script will not help in the event of a hard drive crash. If a 173 | # copy of the backup has not be stored offline or on another PC.. 174 | # You should copy your backups offline regularly for best protection. 175 | # 176 | # Happy backing up... 177 | # 178 | #===================================================================== 179 | # Change Log 180 | #===================================================================== 181 | # 182 | # VER 1.0 - (2005-03-25) 183 | # Initial Release - based on AutoMySQLBackup 2.2 184 | # 185 | #===================================================================== 186 | #===================================================================== 187 | # 188 | # Should not need to be modified from here down!! 189 | # 190 | #===================================================================== 191 | #===================================================================== 192 | PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/postgres/bin:/usr/local/pgsql/bin 193 | DATE=`date +%Y-%m-%d` # Datestamp e.g 2002-09-21 194 | DOW=`date +%A` # Day of the week e.g. Monday 195 | DNOW=`date +%u` # Day number of the week 1 to 7 where 1 represents Monday 196 | DOM=`date +%d` # Date of the Month e.g. 27 197 | M=`date +%B` # Month e.g January 198 | W=`date +%V` # Week Number e.g 37 199 | VER=1.0 # Version Number 200 | LOGFILE=$BACKUPDIR/$DBHOST-`date +%N`.log # Logfile Name 201 | OPT="" # OPT string for use with mysqldump ( see man mysqldump ) 202 | BACKUPFILES="" # thh: added for later mailing 203 | 204 | # Create required directories 205 | if [ ! -e "$BACKUPDIR" ] # Check Backup Directory exists. 206 | then 207 | mkdir -p "$BACKUPDIR" 208 | fi 209 | 210 | if [ ! -e "$BACKUPDIR/daily" ] # Check Daily Directory exists. 211 | then 212 | mkdir -p "$BACKUPDIR/daily" 213 | fi 214 | 215 | if [ ! -e "$BACKUPDIR/weekly" ] # Check Weekly Directory exists. 216 | then 217 | mkdir -p "$BACKUPDIR/weekly" 218 | fi 219 | 220 | if [ ! -e "$BACKUPDIR/monthly" ] # Check Monthly Directory exists. 221 | then 222 | mkdir -p "$BACKUPDIR/monthly" 223 | fi 224 | 225 | 226 | # IO redirection for logging. 227 | touch $LOGFILE 228 | exec 6>&1 # Link file descriptor #6 with stdout. 229 | # Saves stdout. 230 | exec > $LOGFILE # stdout replaced with file $LOGFILE. 231 | 232 | 233 | # Functions 234 | 235 | # Database dump function 236 | dbdump () { 237 | pg_dump --username=$USERNAME $HOST $OPT $1 > $2 238 | return 0 239 | } 240 | 241 | # Compression function 242 | SUFFIX="" 243 | compression () { 244 | if [ "$COMP" = "gzip" ]; then 245 | gzip -f "$1" 246 | echo 247 | echo Backup Information for "$1" 248 | gzip -l "$1.gz" 249 | SUFFIX=".gz" 250 | elif [ "$COMP" = "bzip2" ]; then 251 | echo Compression information for "$1.bz2" 252 | bzip2 -f -v $1 2>&1 253 | SUFFIX=".bz2" 254 | else 255 | echo "No compression option set, check advanced settings" 256 | fi 257 | return 0 258 | } 259 | 260 | 261 | # Run command before we begin 262 | if [ "$PREBACKUP" ] 263 | then 264 | echo ====================================================================== 265 | echo "Prebackup command output." 266 | echo 267 | eval $PREBACKUP 268 | echo 269 | echo ====================================================================== 270 | echo 271 | fi 272 | 273 | 274 | if [ "$SEPDIR" = "yes" ]; then # Check if CREATE DATABSE should be included in Dump 275 | if [ "$CREATE_DATABASE" = "no" ]; then 276 | OPT="$OPT" 277 | else 278 | OPT="$OPT --create" 279 | fi 280 | else 281 | OPT="$OPT" 282 | fi 283 | 284 | # Hostname for LOG information 285 | if [ "$DBHOST" = "localhost" ]; then 286 | DBHOST="`hostname -f`" 287 | HOST="" 288 | else 289 | HOST="-h $DBHOST" 290 | fi 291 | 292 | # If backing up all DBs on the server 293 | if [ "$DBNAMES" = "all" ]; then 294 | DBNAMES="`psql -U $USERNAME $HOST -l -A -F: | sed -ne "/:/ { /Name:Owner/d; /template0/d; s/:.*$//; p }"`" 295 | 296 | # If DBs are excluded 297 | for exclude in $DBEXCLUDE 298 | do 299 | DBNAMES=`echo $DBNAMES | sed "s/\b$exclude\b//g"` 300 | done 301 | 302 | MDBNAMES=$DBNAMES 303 | fi 304 | 305 | echo ====================================================================== 306 | echo AutoPostgreSQLBackup VER $VER 307 | echo http://autopgsqlbackup.frozenpc.net/ 308 | echo 309 | echo Backup of Database Server - $DBHOST 310 | echo ====================================================================== 311 | 312 | # Test is seperate DB backups are required 313 | if [ "$SEPDIR" = "yes" ]; then 314 | echo Backup Start Time `date` 315 | echo ====================================================================== 316 | # Monthly Full Backup of all Databases 317 | if [ $DOM = "01" ]; then 318 | for MDB in $MDBNAMES 319 | do 320 | 321 | # Prepare $DB for using 322 | MDB="`echo $MDB | sed 's/%/ /g'`" 323 | 324 | if [ ! -e "$BACKUPDIR/monthly/$MDB" ] # Check Monthly DB Directory exists. 325 | then 326 | mkdir -p "$BACKUPDIR/monthly/$MDB" 327 | fi 328 | echo Monthly Backup of $MDB... 329 | dbdump "$MDB" "$BACKUPDIR/monthly/$MDB/${MDB}_$DATE.$M.$MDB.sql" 330 | compression "$BACKUPDIR/monthly/$MDB/${MDB}_$DATE.$M.$MDB.sql" 331 | BACKUPFILES="$BACKUPFILES $BACKUPDIR/monthly/$MDB/${MDB}_$DATE.$M.$MDB.sql$SUFFIX" 332 | echo ---------------------------------------------------------------------- 333 | done 334 | fi 335 | 336 | for DB in $DBNAMES 337 | do 338 | # Prepare $DB for using 339 | DB="`echo $DB | sed 's/%/ /g'`" 340 | 341 | # Create Separate directory for each DB 342 | if [ ! -e "$BACKUPDIR/daily/$DB" ] # Check Daily DB Directory exists. 343 | then 344 | mkdir -p "$BACKUPDIR/daily/$DB" 345 | fi 346 | 347 | if [ ! -e "$BACKUPDIR/weekly/$DB" ] # Check Weekly DB Directory exists. 348 | then 349 | mkdir -p "$BACKUPDIR/weekly/$DB" 350 | fi 351 | 352 | # Weekly Backup 353 | if [ $DNOW = $DOWEEKLY ]; then 354 | echo Weekly Backup of Database \( $DB \) 355 | echo Rotating 5 weeks Backups... 356 | if [ "$W" -le 05 ];then 357 | REMW=`expr 48 + $W` 358 | elif [ "$W" -lt 15 ];then 359 | REMW=0`expr $W - 5` 360 | else 361 | REMW=`expr $W - 5` 362 | fi 363 | eval rm -fv "$BACKUPDIR/weekly/$DB/week.$REMW.*" 364 | echo 365 | dbdump "$DB" "$BACKUPDIR/weekly/$DB/${DB}_week.$W.$DATE.sql" 366 | compression "$BACKUPDIR/weekly/$DB/${DB}_week.$W.$DATE.sql" 367 | BACKUPFILES="$BACKUPFILES $BACKUPDIR/weekly/$DB/${DB}_week.$W.$DATE.sql$SUFFIX" 368 | echo ---------------------------------------------------------------------- 369 | 370 | # Daily Backup 371 | else 372 | echo Daily Backup of Database \( $DB \) 373 | echo Rotating last weeks Backup... 374 | eval rm -fv "$BACKUPDIR/daily/$DB/*.$DOW.sql.*" 375 | echo 376 | dbdump "$DB" "$BACKUPDIR/daily/$DB/${DB}_$DATE.$DOW.sql" 377 | compression "$BACKUPDIR/daily/$DB/${DB}_$DATE.$DOW.sql" 378 | BACKUPFILES="$BACKUPFILES $BACKUPDIR/daily/$DB/${DB}_$DATE.$DOW.sql$SUFFIX" 379 | echo ---------------------------------------------------------------------- 380 | fi 381 | done 382 | echo Backup End `date` 383 | echo ====================================================================== 384 | 385 | 386 | else # One backup file for all DBs 387 | echo Backup Start `date` 388 | echo ====================================================================== 389 | # Monthly Full Backup of all Databases 390 | if [ $DOM = "01" ]; then 391 | echo Monthly full Backup of \( $MDBNAMES \)... 392 | dbdump "$MDBNAMES" "$BACKUPDIR/monthly/$DATE.$M.all-databases.sql" 393 | compression "$BACKUPDIR/monthly/$DATE.$M.all-databases.sql" 394 | BACKUPFILES="$BACKUPFILES $BACKUPDIR/monthly/$DATE.$M.all-databases.sql$SUFFIX" 395 | echo ---------------------------------------------------------------------- 396 | fi 397 | 398 | # Weekly Backup 399 | if [ $DNOW = $DOWEEKLY ]; then 400 | echo Weekly Backup of Databases \( $DBNAMES \) 401 | echo 402 | echo Rotating 5 weeks Backups... 403 | if [ "$W" -le 05 ];then 404 | REMW=`expr 48 + $W` 405 | elif [ "$W" -lt 15 ];then 406 | REMW=0`expr $W - 5` 407 | else 408 | REMW=`expr $W - 5` 409 | fi 410 | eval rm -fv "$BACKUPDIR/weekly/week.$REMW.*" 411 | echo 412 | dbdump "$DBNAMES" "$BACKUPDIR/weekly/week.$W.$DATE.sql" 413 | compression "$BACKUPDIR/weekly/week.$W.$DATE.sql" 414 | BACKUPFILES="$BACKUPFILES $BACKUPDIR/weekly/week.$W.$DATE.sql$SUFFIX" 415 | echo ---------------------------------------------------------------------- 416 | 417 | # Daily Backup 418 | else 419 | echo Daily Backup of Databases \( $DBNAMES \) 420 | echo 421 | echo Rotating last weeks Backup... 422 | eval rm -fv "$BACKUPDIR/daily/*.$DOW.sql.*" 423 | echo 424 | dbdump "$DBNAMES" "$BACKUPDIR/daily/$DATE.$DOW.sql" 425 | compression "$BACKUPDIR/daily/$DATE.$DOW.sql" 426 | BACKUPFILES="$BACKUPFILES $BACKUPDIR/daily/$DATE.$DOW.sql$SUFFIX" 427 | echo ---------------------------------------------------------------------- 428 | fi 429 | echo Backup End Time `date` 430 | echo ====================================================================== 431 | fi 432 | echo Total disk space used for backup storage.. 433 | echo Size - Location 434 | echo `du -hs "$BACKUPDIR"` 435 | echo 436 | 437 | 438 | # Run command when we're done 439 | if [ "$POSTBACKUP" ] 440 | then 441 | echo ====================================================================== 442 | echo "Postbackup command output." 443 | echo 444 | eval $POSTBACKUP 445 | echo 446 | echo ====================================================================== 447 | fi 448 | 449 | #Clean up IO redirection 450 | exec 1>&6 6>&- # Restore stdout and close file descriptor #6. 451 | 452 | if [ "$MAILCONTENT" = "files" ] 453 | then 454 | #Get backup size 455 | ATTSIZE=`du -c $BACKUPFILES | grep "[[:digit:][:space:]]total$" |sed s/\s*total//` 456 | if [ $MAXATTSIZE -ge $ATTSIZE ] 457 | then 458 | BACKUPFILES=`echo "$BACKUPFILES" | sed -e "s# # -a #g"` #enable multiple attachments 459 | mutt -s "PostgreSQL Backup Log and SQL Files for $DBHOST - $DATE" $BACKUPFILES $MAILADDR < $LOGFILE #send via mutt 460 | else 461 | cat "$LOGFILE" | mail -s "WARNING! - PostgreSQL Backup exceeds set maximum attachment size on $HOST - $DATE" $MAILADDR 462 | fi 463 | elif [ "$MAILCONTENT" = "log" ] 464 | then 465 | cat "$LOGFILE" | mail -s "PostgreSQL Backup Log for $DBHOST - $DATE" $MAILADDR 466 | else 467 | cat "$LOGFILE" 468 | fi 469 | 470 | # Clean up Logfile 471 | eval rm -f "$LOGFILE" 472 | 473 | exit 0 474 | -------------------------------------------------------------------------------- /server/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim 2 | MAINTAINER Nir Galon 3 | ENV SANIC_REQUEST_TIMEOUT 500 4 | ENV SANIC_RESPONSE_TIMEOUT 500 5 | 6 | RUN apt-get update && apt-get install -y --allow-unauthenticated 7 | 8 | ADD . /code 9 | RUN pip install -r /code/requirements.txt 10 | WORKDIR /code 11 | 12 | EXPOSE 8000 13 | CMD ["python", "-m", "sanic", "main.app", "--host=0.0.0.0", "--port=8000", "--workers=1"] 14 | -------------------------------------------------------------------------------- /server/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | sanic = "*" 8 | aiopg = "*" 9 | peewee-async = "*" 10 | peewee = "*" 11 | sanic-cors = "*" 12 | python-dotenv = "*" 13 | 14 | [dev-packages] 15 | 16 | [requires] 17 | python_version = "3.7" 18 | -------------------------------------------------------------------------------- /server/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "88e741cd7e601b79b35cc345eb8ee144925cab3f8ffc296542a5cd0488e02e96" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.python.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiofiles": { 20 | "hashes": [ 21 | "sha256:021ea0ba314a86027c166ecc4b4c07f2d40fc0f4b3a950d1868a0f2571c2bbee", 22 | "sha256:1e644c2573f953664368de28d2aa4c89dfd64550429d0c27c4680ccd3aa4985d" 23 | ], 24 | "version": "==0.4.0" 25 | }, 26 | "aiopg": { 27 | "hashes": [ 28 | "sha256:32da3c9c73c248c715883fbb0876d66015d9fb6b4946058eb9fd40e86cfff9b4", 29 | "sha256:5da7763028b735ebc4c2980ba39ef1ce4f48df1fc05900e036674edd50f0727c" 30 | ], 31 | "index": "pypi", 32 | "version": "==1.0.0" 33 | }, 34 | "certifi": { 35 | "hashes": [ 36 | "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", 37 | "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" 38 | ], 39 | "version": "==2019.9.11" 40 | }, 41 | "chardet": { 42 | "hashes": [ 43 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 44 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 45 | ], 46 | "version": "==3.0.4" 47 | }, 48 | "h11": { 49 | "hashes": [ 50 | "sha256:acca6a44cb52a32ab442b1779adf0875c443c689e9e028f8d831a3769f9c5208", 51 | "sha256:f2b1ca39bfed357d1f19ac732913d5f9faa54a5062eca7d2ec3a916cfb7ae4c7" 52 | ], 53 | "version": "==0.8.1" 54 | }, 55 | "h2": { 56 | "hashes": [ 57 | "sha256:ac377fcf586314ef3177bfd90c12c7826ab0840edeb03f0f24f511858326049e", 58 | "sha256:b8a32bd282594424c0ac55845377eea13fa54fe4a8db012f3a198ed923dc3ab4" 59 | ], 60 | "version": "==3.1.1" 61 | }, 62 | "hpack": { 63 | "hashes": [ 64 | "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89", 65 | "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2" 66 | ], 67 | "version": "==3.0.0" 68 | }, 69 | "httpcore": { 70 | "hashes": [ 71 | "sha256:96f910b528d47b683242ec207050c7bbaa99cd1b9a07f78ea80cf61e55556b58" 72 | ], 73 | "version": "==0.3.0" 74 | }, 75 | "httptools": { 76 | "hashes": [ 77 | "sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc" 78 | ], 79 | "version": "==0.0.13" 80 | }, 81 | "hyperframe": { 82 | "hashes": [ 83 | "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40", 84 | "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f" 85 | ], 86 | "version": "==5.2.0" 87 | }, 88 | "idna": { 89 | "hashes": [ 90 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 91 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 92 | ], 93 | "version": "==2.8" 94 | }, 95 | "multidict": { 96 | "hashes": [ 97 | "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", 98 | "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", 99 | "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", 100 | "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", 101 | "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", 102 | "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", 103 | "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", 104 | "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", 105 | "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", 106 | "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", 107 | "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", 108 | "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", 109 | "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", 110 | "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", 111 | "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", 112 | "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", 113 | "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", 114 | "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", 115 | "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", 116 | "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", 117 | "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", 118 | "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", 119 | "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", 120 | "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", 121 | "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", 122 | "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", 123 | "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", 124 | "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", 125 | "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" 126 | ], 127 | "version": "==4.5.2" 128 | }, 129 | "peewee": { 130 | "hashes": [ 131 | "sha256:2342067f48a779e35956a44cd547df883dda35153daa9fe994d970585aaec281" 132 | ], 133 | "index": "pypi", 134 | "version": "==2.10.2" 135 | }, 136 | "peewee-async": { 137 | "hashes": [ 138 | "sha256:1376774637b6f5cfb9192a06380a8e987fed206e0e229bbadd50da6a4578557b", 139 | "sha256:ab64a2a376033ce5621406b33735cb064659af05f5c2570af0fba08f6eab6282" 140 | ], 141 | "index": "pypi", 142 | "version": "==0.5.12" 143 | }, 144 | "psycopg2-binary": { 145 | "hashes": [ 146 | "sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809", 147 | "sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598", 148 | "sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5", 149 | "sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1", 150 | "sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d", 151 | "sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e", 152 | "sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00", 153 | "sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf", 154 | "sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43", 155 | "sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5", 156 | "sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70", 157 | "sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6", 158 | "sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd", 159 | "sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877", 160 | "sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3", 161 | "sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67", 162 | "sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68", 163 | "sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b", 164 | "sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a", 165 | "sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b", 166 | "sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2", 167 | "sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e", 168 | "sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e", 169 | "sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f", 170 | "sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f", 171 | "sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7", 172 | "sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737", 173 | "sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7" 174 | ], 175 | "version": "==2.8.3" 176 | }, 177 | "python-dotenv": { 178 | "hashes": [ 179 | "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093", 180 | "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544" 181 | ], 182 | "index": "pypi", 183 | "version": "==0.10.3" 184 | }, 185 | "requests": { 186 | "hashes": [ 187 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 188 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 189 | ], 190 | "version": "==2.22.0" 191 | }, 192 | "requests-async": { 193 | "hashes": [ 194 | "sha256:8731420451383196ecf2fd96082bfc8ae5103ada90aba185888499d7784dde6f" 195 | ], 196 | "version": "==0.5.0" 197 | }, 198 | "rfc3986": { 199 | "hashes": [ 200 | "sha256:0344d0bd428126ce554e7ca2b61787b6a28d2bbd19fc70ed2dd85efe31176405", 201 | "sha256:df4eba676077cefb86450c8f60121b9ae04b94f65f85b69f3f731af0516b7b18" 202 | ], 203 | "version": "==1.3.2" 204 | }, 205 | "sanic": { 206 | "hashes": [ 207 | "sha256:254587b7e7658e4ec1aa928b110620617d79eca917e4370bcf2b4ed9b6c6382c", 208 | "sha256:5bd3314fd5dc8cafef7a1aff69ec7547c404d82b90a7b78796d81cb138a3bc0e" 209 | ], 210 | "index": "pypi", 211 | "version": "==19.6.3" 212 | }, 213 | "sanic-cors": { 214 | "hashes": [ 215 | "sha256:28f7a87304fd69024e141294f60bff3066cb11eac4aae8c5a80a6b3f8069698e", 216 | "sha256:4a93f62ded2a9e91370e159c9791039aadc68593149cf903bddaf470b6e98d4a" 217 | ], 218 | "index": "pypi", 219 | "version": "==0.9.9.post3" 220 | }, 221 | "sanic-plugins-framework": { 222 | "hashes": [ 223 | "sha256:7bfa4d4e2b7e0ff487b2297d3e8b8697f6bc49b0c2c3967289716d30cb44d881", 224 | "sha256:9b1f8f3fa8dcbcc0397adafe919665cce10b82cc41d543ee2f317ac31513a78d" 225 | ], 226 | "version": "==0.8.2" 227 | }, 228 | "ujson": { 229 | "hashes": [ 230 | "sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86" 231 | ], 232 | "markers": "sys_platform != 'win32' and implementation_name == 'cpython'", 233 | "version": "==1.35" 234 | }, 235 | "urllib3": { 236 | "hashes": [ 237 | "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", 238 | "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" 239 | ], 240 | "version": "==1.25.6" 241 | }, 242 | "uvloop": { 243 | "hashes": [ 244 | "sha256:0deb6c97c5807c792dd9024bab90e6ca49e981862103cb2ea37b430c1ca0a267", 245 | "sha256:155b34d513655e753d07f499a7e811970e2d397f240dfcbec0b32a9587159c99", 246 | "sha256:1df3ddfa280206e9999ae1c777a20836eb895bcec6dc9fae2cbb6eecfafb099e", 247 | "sha256:5b19361c8767e1dc61f6367f948d4f3dc5504b9f2eba488641b3d26ec14498ba", 248 | "sha256:942cd07035510b149d6160796f4e972137130ae953871b6a98c2cf5d5ab68c2e", 249 | "sha256:c63b6c0bf33144c604dd72f7eecf2d3a3ac7405c503c67bd98128cf306efe18a", 250 | "sha256:e698a20a3b4ccb380d207f9d491d4085d7c38d364f6a0bae98684a1612a9607a" 251 | ], 252 | "markers": "sys_platform != 'win32' and implementation_name == 'cpython'", 253 | "version": "==0.13.0" 254 | }, 255 | "websockets": { 256 | "hashes": [ 257 | "sha256:04b42a1b57096ffa5627d6a78ea1ff7fad3bc2c0331ffc17bc32a4024da7fea0", 258 | "sha256:08e3c3e0535befa4f0c4443824496c03ecc25062debbcf895874f8a0b4c97c9f", 259 | "sha256:10d89d4326045bf5e15e83e9867c85d686b612822e4d8f149cf4840aab5f46e0", 260 | "sha256:232fac8a1978fc1dead4b1c2fa27c7756750fb393eb4ac52f6bc87ba7242b2fa", 261 | "sha256:4bf4c8097440eff22bc78ec76fe2a865a6e658b6977a504679aaf08f02c121da", 262 | "sha256:51642ea3a00772d1e48fb0c492f0d3ae3b6474f34d20eca005a83f8c9c06c561", 263 | "sha256:55d86102282a636e195dad68aaaf85b81d0bef449d7e2ef2ff79ac450bb25d53", 264 | "sha256:564d2675682bd497b59907d2205031acbf7d3fadf8c763b689b9ede20300b215", 265 | "sha256:5d13bf5197a92149dc0badcc2b699267ff65a867029f465accfca8abab95f412", 266 | "sha256:5eda665f6789edb9b57b57a159b9c55482cbe5b046d7db458948370554b16439", 267 | "sha256:5edb2524d4032be4564c65dc4f9d01e79fe8fad5f966e5b552f4e5164fef0885", 268 | "sha256:79691794288bc51e2a3b8de2bc0272ca8355d0b8503077ea57c0716e840ebaef", 269 | "sha256:7fcc8681e9981b9b511cdee7c580d5b005f3bb86b65bde2188e04a29f1d63317", 270 | "sha256:8e447e05ec88b1b408a4c9cde85aa6f4b04f06aa874b9f0b8e8319faf51b1fee", 271 | "sha256:90ea6b3e7787620bb295a4ae050d2811c807d65b1486749414f78cfd6fb61489", 272 | "sha256:9e13239952694b8b831088431d15f771beace10edfcf9ef230cefea14f18508f", 273 | "sha256:d40f081187f7b54d7a99d8a5c782eaa4edc335a057aa54c85059272ed826dc09", 274 | "sha256:e1df1a58ed2468c7b7ce9a2f9752a32ad08eac2bcd56318625c3647c2cd2da6f", 275 | "sha256:e98d0cec437097f09c7834a11c69d79fe6241729b23f656cfc227e93294fc242", 276 | "sha256:f8d59627702d2ff27cb495ca1abdea8bd8d581de425c56e93bff6517134e0a9b", 277 | "sha256:fc30cdf2e949a2225b012a7911d1d031df3d23e99b7eda7dfc982dc4a860dae9" 278 | ], 279 | "version": "==7.0" 280 | } 281 | }, 282 | "develop": {} 283 | } 284 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | ## Server 3 | 4 | The server (backend) side of the project written [Python](https://www.python.org/) using [Sanic](https://github.com/channelcat/sanic). 5 | ======= 6 | ## server 7 | 8 | The server (backend) side of the project written [Python](https://www.python.org/) using [Django 1.10.5](https://www.djangoproject.com/), [Django REST framework](http://www.django-rest-framework.org/). 9 | >>>>>>> b72a69f (Update dependencies (#20)) 10 | 11 | ## Prerequisites 12 | 13 | * Install [Python 3.x](https://www.python.org/) 14 | <<<<<<< HEAD 15 | * Install [Pipenv](https://github.com/pypa/pipenv) 16 | ======= 17 | >>>>>>> b72a69f (Update dependencies (#20)) 18 | * Install [Pip](https://pypi.python.org/pypi/pip) 19 | 20 | ## Manual Installation 21 | 22 | <<<<<<< HEAD 23 | 1. Create a file called `.env` at the root of the project and write the configurations of the server there. Here is an example of the `.env` file: 24 | ``` 25 | DB_URI = 'postgres://nirgalon:@localhost:5432/assp' 26 | DB_NAME = 'assp' 27 | DB_USER = 'nirgalon' 28 | DB_PASSWORD = 'nirgalon' 29 | DB_PORT = '5432' 30 | HOST = '0.0.0.0' 31 | PORT = '8000' 32 | LOG_LEVEL = 'info' 33 | ENV = 'development' 34 | ``` 35 | 2. Create a virtual environment with `pipenv install` at the root of the project. 36 | 3. Get in to the virtual environment with `pipenv shell`. 37 | 4. Run the server with the `.env` file `python main.py` 38 | 5. Hit [http://localhost:8000](http://localhost:8000) with Postman. 39 | 40 | ## Tests 41 | 42 | To run all tests `python -m unittest` 43 | ======= 44 | 1. `cd server/config` then `cp local_settings.template local_settings.py` and modify it by your local settings 45 | 2. Install requirements with `pip install -r requirements.txt` 46 | 3. Migrate the data with `python manage.py migrate` 47 | 4. Run the server with `python manage.py runserver` 48 | 5. Open the browser at [http://localhost:8000](http://localhost:8000) 49 | 50 | ## Tests 51 | 52 | * Run `pycodestyle --show-source --max-line-length=119 --show-pep8 server;` to check for lint mistakes (by PEP8) 53 | * Run `python manage.py test` to execute the unit tests 54 | 55 | ## Deploy 56 | 57 | 1. Not Yet 58 | >>>>>>> b72a69f (Update dependencies (#20)) 59 | -------------------------------------------------------------------------------- /server/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/server/api/__init__.py -------------------------------------------------------------------------------- /server/api/tests.py: -------------------------------------------------------------------------------- 1 | from rest_framework import status 2 | from rest_framework.test import APITestCase 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class UsersApiTestCase(APITestCase): 7 | def setUp(self): 8 | User.objects.create_superuser('admin', 'admin@example.com', 'admin12345') 9 | 10 | def test_get_users_objects(self): 11 | response = self.client.get('/api/users/', format='json') 12 | self.assertEqual(response.status_code, status.HTTP_200_OK) 13 | self.assertEqual(len(response.data), 1) 14 | self.assertEqual(response.data[0]['email'], 'admin@example.com') 15 | self.assertEqual(response.data[0]['username'], 'admin') 16 | self.assertEqual(response.data[0]['url'], 'http://testserver/api/users/1/') 17 | -------------------------------------------------------------------------------- /server/api/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/server/api/users/__init__.py -------------------------------------------------------------------------------- /server/api/users/model.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | from datetime import date 3 | from playhouse.db_url import connect 4 | from dotenv import load_dotenv, find_dotenv 5 | from peewee import Model, CharField, TextField, DateField 6 | 7 | load_dotenv(find_dotenv()) 8 | psql_db = connect(getenv('DB_URI')) 9 | 10 | 11 | class User(Model): 12 | name = CharField(max_length=40) 13 | age = TextField(default=0) 14 | email = CharField(unique=True, null=False) 15 | date_joined = DateField(default=date.today()) 16 | 17 | class Meta: 18 | database = psql_db 19 | -------------------------------------------------------------------------------- /server/api/users/routes.py: -------------------------------------------------------------------------------- 1 | from sanic import Blueprint 2 | 3 | from api.users.view import UsersView, GetUsersView 4 | 5 | users_routes = Blueprint('users_routes', url_prefix='/users', strict_slashes=True) 6 | 7 | users_routes.add_route(UsersView.as_view(), '') 8 | users_routes.add_route(GetUsersView.as_view(), '/') 9 | -------------------------------------------------------------------------------- /server/api/users/tests.py: -------------------------------------------------------------------------------- 1 | from main import app 2 | 3 | 4 | def test_new_user(): 5 | request, response = app.test_client.get('/users') 6 | print("request", request) 7 | print("response", response) 8 | assert response.status == 404 9 | -------------------------------------------------------------------------------- /server/api/users/view.py: -------------------------------------------------------------------------------- 1 | from json import loads 2 | from sanic.views import HTTPMethodView 3 | from sanic.response import json, text 4 | from peewee import IntegrityError 5 | 6 | from api.users.model import User 7 | 8 | 9 | class UsersView(HTTPMethodView): 10 | async def get(self, request): 11 | users = User.select() 12 | return json({ 13 | '_message': 'Users fetched successfully!', 14 | 'users': users, 15 | }) 16 | 17 | async def post(self, request): 18 | try: 19 | new_user = User.create(**loads(request.body)) 20 | return json({ 21 | '_message': 'User created successfully!', 22 | 'user': new_user, 23 | }) 24 | except IntegrityError as e: 25 | if "email" in str(e): 26 | return json({ 27 | '_message': 'Should fill out email field', 28 | 'user': None, 29 | }) 30 | 31 | async def put(self, request): 32 | return text('I am put method') 33 | 34 | async def delete(self, request): 35 | return text('I am delete method') 36 | 37 | 38 | class GetUsersView(HTTPMethodView): 39 | async def get(self, request, id): 40 | try: 41 | user = User.get(User.id == id) 42 | return json({ 43 | '_message': 'User fetched successfully!', 44 | 'user': user 45 | }) 46 | except User.DoesNotExist: 47 | return json({ 48 | '_message': 'User does not exist', 49 | 'user': None 50 | }) 51 | -------------------------------------------------------------------------------- /server/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: 'gcr.io/cloud-builders/docker' 3 | args: ['build', '-t', 'gcr.io/$PROJECT_ID/raptor:$SHORT_SHA', '.'] 4 | - name: 'gcr.io/cloud-builders/docker' 5 | args: ['push', 'gcr.io/$PROJECT_ID/raptor:$SHORT_SHA'] 6 | - name: gcr.io/cloud-builders/wget 7 | args: ['-O', 'sigil.tgz', 'https://github.com/gliderlabs/sigil/releases/download/v0.4.0/sigil_0.4.0_Linux_x86_64.tgz'] 8 | - name: 'gcr.io/cloud-builders/gcloud' 9 | entrypoint: 'bash' 10 | args: 11 | - '-c' 12 | - | 13 | export APP_VERSION 14 | export NAMESPACE 15 | export CLUSTER_NAME 16 | IFS=/ read -r APP_VERSION CLUSTER_NAME NAMESPACE <<< "$TAG_NAME" 17 | gcloud components install kubectl 18 | gcloud container clusters get-credentials $_CLUSTER_NAME --zone $_ZONE_NAME 19 | export TAG_NAME=$TAG_NAME 20 | export PROJECT_ID=$PROJECT_ID 21 | export ENVIRONMENT=$_ENVIRONMENT 22 | export SHORT_SHA=$SHORT_SHA 23 | tar zxvf sigil.tgz 24 | ./sigil -p -f kubernetes/server.yaml | kubectl apply -f - 25 | -------------------------------------------------------------------------------- /server/config/local_settings.template: -------------------------------------------------------------------------------- 1 | import os 2 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 3 | 4 | # Enable debug trace. 5 | DEBUG = True 6 | 7 | # SECURITY WARNING: keep the secret key used in production secret! 8 | SECRET_KEY = '{{secret_key}}' 9 | 10 | # Static files (CSS, JavaScript, Images) 11 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 12 | STATIC_URL = '/static/' 13 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 14 | MEDIA_URL = '/staticuploads/' 15 | MEDIA_ROOT = os.path.join(BASE_DIR, 'staticuploads') 16 | 17 | DATABASES = { 18 | 'default': { 19 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 20 | 'NAME': _user, 21 | 'USER': _user, 22 | 'PASSWORD': _user, 23 | 'HOST': 'localhost', 24 | 'PORT': '', 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '-#&29$6a5n)za$%q-9di^$lxu5bvo!so=0e$anel3o7^3oscz6' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | INSTALLED_APPS = [ 33 | 'api', 34 | 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 42 | 'admin_honeypot', 43 | 'django_extensions', 44 | 'rest_framework', 45 | 'corsheaders', 46 | ] 47 | 48 | MIDDLEWARE = [ 49 | 'django.middleware.security.SecurityMiddleware', 50 | 'django.contrib.sessions.middleware.SessionMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | 'corsheaders.middleware.CorsMiddleware', 57 | ] 58 | 59 | ROOT_URLCONF = 'config.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': [], 65 | 'APP_DIRS': True, 66 | 'OPTIONS': { 67 | 'context_processors': [ 68 | 'django.template.context_processors.debug', 69 | 'django.template.context_processors.request', 70 | 'django.contrib.auth.context_processors.auth', 71 | 'django.contrib.messages.context_processors.messages', 72 | ], 73 | }, 74 | }, 75 | ] 76 | 77 | WSGI_APPLICATION = 'config.wsgi.application' 78 | 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 82 | DATABASES = { 83 | 'default': { 84 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 85 | 'NAME': 'server', 86 | 'USER': 'postgres', 87 | 'PASSWORD': '', 88 | 'HOST': 'localhost', 89 | 'PORT': 5432, 90 | } 91 | } 92 | 93 | 94 | # Password validation 95 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 96 | AUTH_PASSWORD_VALIDATORS = [ 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 108 | }, 109 | ] 110 | 111 | 112 | # Internationalization 113 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 114 | LANGUAGE_CODE = 'en-us' 115 | TIME_ZONE = 'UTC' 116 | USE_I18N = True 117 | USE_L10N = True 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 123 | STATIC_URL = '/static/' 124 | 125 | 126 | # Importing local settings if exists. 127 | try: 128 | from .local_settings import * 129 | except ImportError: 130 | pass 131 | -------------------------------------------------------------------------------- /server/config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.conf import settings 18 | from django.conf.urls import url, include 19 | from django.conf.urls.static import static 20 | 21 | urlpatterns = [ 22 | url(r'^api/', include('api.urls')), 23 | url(r'^admin/', include('admin_honeypot.urls', namespace='admin_honeypot')), 24 | url(r'^secret-admin/', admin.site.urls), 25 | ] 26 | 27 | # Serve static uploaded files if in debug mode. 28 | # if settings.DEBUG: 29 | # urlpatterns = urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 30 | -------------------------------------------------------------------------------- /server/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /server/kubernetes/helm-permissions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: tiller 5 | namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1beta1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: tiller 11 | namespace: kube-system 12 | roleRef: 13 | apiGroup: rbac.authorization.k8s.io 14 | kind: ClusterRole 15 | name: cluster-admin 16 | subjects: 17 | - kind: ServiceAccount 18 | name: tiller 19 | namespace: kube-system 20 | -------------------------------------------------------------------------------- /server/kubernetes/server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: $NAMESPACE 5 | labels: 6 | name: $NAMESPACE 7 | --- 8 | apiVersion: extensions/v1beta1 9 | kind: Deployment 10 | metadata: 11 | name: server 12 | namespace: $NAMESPACE 13 | labels: 14 | app: server 15 | spec: 16 | replicas: 1 17 | revisionHistoryLimit: 0 18 | selector: 19 | matchLabels: 20 | app: server 21 | strategy: 22 | type: RollingUpdate 23 | rollingUpdate: 24 | maxSurge: 0 25 | maxUnavailable: 1 26 | template: 27 | metadata: 28 | labels: 29 | app: server 30 | version: $SHORT_SHA 31 | spec: 32 | containers: 33 | - name: server 34 | image: 'gcr.io/$PROJECT_ID/server:$SHORT_SHA' 35 | ports: 36 | - containerPort: 8000 37 | env: 38 | - name: SANIC_REQUEST_TIMEOUT 39 | value: "500" 40 | - name: SANIC_RESPONSE_TIMEOUT 41 | value: "500" 42 | - name: SANIC_KEEP_ALIVE_TIMEOUT 43 | value: "500" 44 | - name: DB_URI 45 | value: "postgres://nirgalon:@localhost:5432/assp" 46 | - name: DB_URI 47 | value: "postgres://nirgalon:@localhost:5432/assp" 48 | - name: DB_NAME 49 | value: "assp" 50 | - name: DB_USER 51 | value: "postgres" 52 | - name: DB_PASSWORD 53 | value: "postgres" 54 | - name: DB_PORT 55 | value: "postgres" 56 | - name: HOST 57 | value: "0.0.0.0" 58 | - name: PORT 59 | value: "8000" 60 | - name: LOG_LEVEL 61 | value: "info" 62 | - name: ENV 63 | value: "production" 64 | livenessProbe: 65 | httpGet: 66 | path: / 67 | port: 8000 68 | initialDelaySeconds: 30 69 | --- 70 | apiVersion: v1 71 | kind: Service 72 | metadata: 73 | name: server-service 74 | namespace: $NAMESPACE 75 | labels: 76 | name: server-service 77 | spec: 78 | ports: 79 | - port: 80 80 | targetPort: 8000 81 | protocol: TCP 82 | name: http 83 | selector: 84 | app: server 85 | type: ClusterIP 86 | --- 87 | apiVersion: extensions/v1beta1 88 | kind: Ingress 89 | metadata: 90 | name: server-ingress 91 | namespace: $NAMESPACE 92 | annotations: 93 | # Annotations for Nginx ingress controller: 94 | nginx.ingress.kubernetes.io/ssl-redirect: "false" 95 | nginx.ingress.kubernetes.io/rewrite-target: / 96 | spec: 97 | rules: 98 | - host: $NAMESPACE.api.$CLUSTER_NAME.theraptr.io 99 | http: 100 | paths: 101 | - path: /server 102 | backend: 103 | serviceName: server-service 104 | servicePort: 80 105 | -------------------------------------------------------------------------------- /server/main.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | from dotenv import load_dotenv, find_dotenv 3 | from sanic import Sanic 4 | from sanic.response import json 5 | from sanic_cors import CORS 6 | 7 | from api.users.routes import users_routes 8 | from util.config import connect_to_db 9 | from util.seed import seed_db 10 | 11 | load_dotenv(find_dotenv()) 12 | 13 | # Start the app. 14 | app = Sanic(load_env=True) 15 | CORS(app) 16 | 17 | # Connect to the database and seed it. 18 | connect_to_db() 19 | seed_db() 20 | 21 | # root route 22 | @app.route('/') 23 | async def root(request): 24 | return json({}, status=200) 25 | 26 | # Register all blueprints. 27 | app.blueprint(users_routes) 28 | 29 | if __name__ == '__main__': 30 | app.run(host=getenv('HOST'), port=getenv('PORT')) 31 | -------------------------------------------------------------------------------- /server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | aiofiles==0.4.0 3 | aiopg==1.0.0 4 | certifi==2019.9.11 5 | chardet==3.0.4 6 | h11==0.8.1 7 | h2==3.1.1 8 | hpack==3.0.0 9 | httpcore==0.3.0 10 | httptools==0.0.13 11 | hyperframe==5.2.0 12 | idna==2.8 13 | multidict==4.5.2 14 | peewee==2.10.2 15 | peewee-async==0.5.12 16 | psycopg2-binary==2.8.3 17 | requests==2.22.0 18 | requests-async==0.5.0 19 | rfc3986==1.3.2 20 | sanic==19.6.3 21 | Sanic-Cors==0.9.9.post3 22 | Sanic-Plugins-Framework==0.8.2 23 | ujson==1.35 24 | urllib3==1.25.6 25 | uvloop==0.13.0 26 | websockets==7.0 27 | ======= 28 | # Install with: `pip install -r requirements.txt` 29 | 30 | Django==1.10.5 31 | psycopg2==2.6.2 32 | django-extensions==1.7.6 33 | gunicorn==19.6.0 34 | 35 | # Security 36 | django-admin-honeypot==1.0.0 37 | 38 | # Rest Framework 39 | djangorestframework==3.5.3 40 | Markdown==2.6.8 41 | django-filter==1.0.1 42 | 43 | # CORS 44 | django-cors-headers==2.0.1 45 | >>>>>>> b72a69f (Update dependencies (#20)) 46 | -------------------------------------------------------------------------------- /server/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirgn975/angular-sanic-seed-project/650134cd3714ef7c247257cd2758b8afae93751e/server/util/__init__.py -------------------------------------------------------------------------------- /server/util/config.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | from peewee import PostgresqlDatabase 3 | 4 | 5 | def connect_to_db(): 6 | global psql_db 7 | psql_db = PostgresqlDatabase( 8 | database=getenv('DB_NAME'), 9 | host=getenv('HOST'), 10 | port=getenv('DB_PORT'), 11 | user=getenv('DB_USER'), 12 | password=getenv('DB_PASSWORD'), 13 | ) 14 | psql_db.connect() 15 | -------------------------------------------------------------------------------- /server/util/seed.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | from peewee import OperationalError 3 | from playhouse.db_url import connect 4 | 5 | from api.users.model import User 6 | 7 | psql_db = connect(getenv('DB_URI')) 8 | 9 | 10 | def seed_db(): 11 | ''' 12 | Create all the models tables, and seed them with some dummy data. 13 | ''' 14 | # Create the tables. 15 | try: 16 | psql_db.create_tables([User], True) 17 | except OperationalError as e: 18 | print("cannot create postgres tables", e) 19 | 20 | # Create dummy data. 21 | User.create(name="nir", age="29", email="nir@example.com") 22 | --------------------------------------------------------------------------------