├── .gitignore ├── LICENSE ├── README.md ├── Vagrantfile ├── backend ├── .gitignore ├── README.md ├── api │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests │ │ ├── __init__.py │ │ └── test_profile.py │ ├── urls.py │ └── views.py ├── backend │ ├── __init__.py │ ├── celery.py │ ├── settings │ │ ├── __init__.py │ │ ├── base.py │ │ ├── dev.py │ │ ├── local.py │ │ └── prod.py │ ├── urls.py │ └── wsgi.py ├── db.sqlite3 ├── infrastructure │ ├── __init__.py │ ├── apps.py │ ├── test_cases.py │ └── tests │ │ └── __init__.py ├── manage.py ├── requirements │ ├── requirements-dev.txt │ └── requirements.txt ├── static │ ├── collect │ │ └── .gitignore │ └── src │ │ └── .gitignore └── tox.ini ├── devops ├── docs └── aws.md ├── frontend ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .stylelintrc ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── gulpfile.ts ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src │ └── client │ │ ├── app │ │ ├── +about │ │ │ ├── about.component.css │ │ │ ├── about.component.e2e-spec.ts │ │ │ ├── about.component.html │ │ │ ├── about.component.spec.ts │ │ │ ├── about.component.ts │ │ │ └── index.ts │ │ ├── +home │ │ │ ├── home.component.css │ │ │ ├── home.component.e2e-spec.ts │ │ │ ├── home.component.html │ │ │ ├── home.component.spec.ts │ │ │ ├── home.component.ts │ │ │ └── index.ts │ │ ├── app.component.e2e-spec.ts │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── hot_loader_main.ts │ │ ├── main.ts │ │ └── shared │ │ │ ├── index.ts │ │ │ ├── name-list │ │ │ ├── index.ts │ │ │ ├── name-list.service.spec.ts │ │ │ └── name-list.service.ts │ │ │ ├── navbar │ │ │ ├── index.ts │ │ │ ├── navbar.component.css │ │ │ ├── navbar.component.html │ │ │ └── navbar.component.ts │ │ │ └── toolbar │ │ │ ├── index.ts │ │ │ ├── toolbar.component.css │ │ │ ├── toolbar.component.html │ │ │ └── toolbar.component.ts │ │ ├── assets │ │ ├── data.json │ │ └── svg │ │ │ └── more.svg │ │ ├── css │ │ └── main.css │ │ ├── index.html │ │ ├── tsconfig.json │ │ └── typings.d.ts ├── test-main.js ├── tools │ ├── .gitignore │ ├── README.md │ ├── config.ts │ ├── config │ │ ├── project.config.ts │ │ ├── seed.config.interfaces.ts │ │ └── seed.config.ts │ ├── debug.ts │ ├── manual_typings │ │ ├── project │ │ │ └── sample.package.d.ts │ │ └── seed │ │ │ ├── angular2-hot-loader.d.ts │ │ │ ├── autoprefixer.d.ts │ │ │ ├── colorguard.d.ts │ │ │ ├── connect-livereload.d.ts │ │ │ ├── cssnano.d.ts │ │ │ ├── doiuse.d.ts │ │ │ ├── express-history-api-fallback.d.ts │ │ │ ├── istream.d.ts │ │ │ ├── karma.d.ts │ │ │ ├── merge-stream.d.ts │ │ │ ├── open.d.ts │ │ │ ├── postcss-reporter.d.ts │ │ │ ├── slash.d.ts │ │ │ ├── stylelint.d.ts │ │ │ ├── systemjs-builder.d.ts │ │ │ ├── tildify.d.ts │ │ │ ├── tiny-lr.d.ts │ │ │ └── walk.d.ts │ ├── tasks │ │ ├── project │ │ │ └── sample.task.ts │ │ └── seed │ │ │ ├── build.assets.dev.ts │ │ │ ├── build.assets.prod.ts │ │ │ ├── build.bundles.app.ts │ │ │ ├── build.bundles.ts │ │ │ ├── build.docs.ts │ │ │ ├── build.html_css.ts │ │ │ ├── build.index.dev.ts │ │ │ ├── build.index.prod.ts │ │ │ ├── build.js.dev.ts │ │ │ ├── build.js.e2e.ts │ │ │ ├── build.js.prod.ts │ │ │ ├── build.js.test.ts │ │ │ ├── build.js.tools.ts │ │ │ ├── check.versions.ts │ │ │ ├── clean.all.ts │ │ │ ├── clean.dev.ts │ │ │ ├── clean.prod.ts │ │ │ ├── clean.tools.ts │ │ │ ├── copy.js.prod.ts │ │ │ ├── css-lint.ts │ │ │ ├── e2e.ts │ │ │ ├── generate.manifest.ts │ │ │ ├── karma.start.ts │ │ │ ├── serve.coverage.ts │ │ │ ├── serve.docs.ts │ │ │ ├── server.prod.ts │ │ │ ├── server.start.ts │ │ │ ├── tslint.ts │ │ │ ├── watch.dev.ts │ │ │ ├── watch.e2e.ts │ │ │ ├── watch.test.ts │ │ │ └── webdriver.ts │ ├── utils.ts │ └── utils │ │ ├── project.utils.ts │ │ ├── project │ │ └── sample_util.ts │ │ ├── seed.utils.ts │ │ └── seed │ │ ├── clean.ts │ │ ├── code_change_tools.ts │ │ ├── server.ts │ │ ├── tasks_tools.ts │ │ ├── template_locals.ts │ │ ├── tsproject.ts │ │ └── watch.ts ├── tsconfig.json ├── tslint.json └── typings.json ├── provisioning ├── ansible │ ├── dbservers.yml │ ├── env_vars │ │ ├── base.yml │ │ ├── dev.yml │ │ ├── prod.yml │ │ └── vagrant.yml │ ├── inventories │ │ ├── dev │ │ ├── prod │ │ └── vagrant │ ├── remote.yml │ ├── roles │ │ ├── base │ │ │ ├── tasks │ │ │ │ ├── create_swap_file.yml │ │ │ │ └── main.yml │ │ │ └── vars │ │ │ │ └── main.yml │ │ ├── celery │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ │ ├── copy_scripts.yml │ │ │ │ ├── main.yml │ │ │ │ └── setup_supervisor.yml │ │ │ ├── templates │ │ │ │ ├── celery_start.j2 │ │ │ │ └── supervisor_celery.conf.j2 │ │ │ └── vars │ │ │ │ └── main.yml │ │ ├── db │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ │ └── main.yml │ │ │ └── vars │ │ │ │ └── main.yml │ │ ├── memcached │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ │ └── main.yml │ │ │ ├── templates │ │ │ │ └── memcached.conf.j2 │ │ │ └── vars │ │ │ │ └── main.yml │ │ ├── rabbitmq │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ │ ├── main.yml │ │ │ │ ├── setup_users.yml │ │ │ │ └── setup_vhosts.yml │ │ │ └── vars │ │ │ │ └── main.yml │ │ └── web │ │ │ ├── handlers │ │ │ └── main.yml │ │ │ ├── tasks │ │ │ ├── create_users_and_groups.yml │ │ │ ├── install_additional_packages.yml │ │ │ ├── main.yml │ │ │ ├── set_file_permissions.yml │ │ │ ├── setup_django_app.yml │ │ │ ├── setup_nginx.yml │ │ │ ├── setup_supervisor.yml │ │ │ └── setup_virtualenv.yml │ │ │ ├── templates │ │ │ ├── gunicorn_start.j2 │ │ │ ├── maintenance_off.html │ │ │ ├── nginx_site_config.j2 │ │ │ ├── supervisor_config.j2 │ │ │ └── virtualenv_postactivate.j2 │ │ │ └── vars │ │ │ └── main.yml │ ├── vagrant.yml │ └── webservers.yml ├── packer │ ├── dev.rds.json │ ├── pack.json │ └── prod.rds.json └── terraform │ ├── backend │ ├── base │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── dev │ │ ├── main.tf │ │ ├── packer.tfvars │ │ └── terraform.tfstate │ └── prod │ │ ├── main.tf │ │ ├── packer.tfvars │ │ └── terraform.tfstate │ ├── core │ ├── core.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── terraform.tfstate │ └── variables.tf │ ├── frontend │ ├── base │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── dev │ │ ├── main.tf │ │ └── terraform.tfstate │ └── prod │ │ ├── main.tf │ │ └── terraform.tfstate │ └── rds │ ├── base │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ ├── dev │ ├── main.tf │ └── terraform.tfstate │ └── prod │ ├── main.tf │ └── terraform.tfstate ├── scripts ├── pipe.py ├── setup.sh ├── temp.sh ├── tools.sh └── vagrant.sh └── vars /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .vagrant/ 4 | *.log 5 | *.backup 6 | *.retry 7 | *.terraform 8 | frontend-bak/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © Phivos Stylianides 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-angular2-fullstack-devops 2 | All-in-one django/angular2 seed with cli interface for multi-environment devops on aws using ansible/packer/terraform. 3 | 4 | Save weeks or even months of work! 5 | 6 | ## Features 7 | * Vagrant box 8 | * Ansible playbook 9 | * Devops cli interface 10 | * Travis CI configurations 11 | * Tests with code coverage 12 | * Isolated frontend/backend projects 13 | * Build virtual images with packer 14 | * Launch infrastructure using terraform 15 | * Central project variables configuration 16 | * Jenkins pipeline workflow server (TODO) 17 | 18 | ## Stack 19 | 20 | ### Frontend 21 | * Angular 2.0 22 | * TypeScript 1.8 23 | * Bootstrap 3.3 24 | 25 | ### Backend 26 | * Django 1.9 27 | * PostgreSQL 9.4 28 | * Django REST Framework 3.3 29 | 30 | ## Requirements 31 | * Git ([Install](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git), [Configure SSH agent](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/)) 32 | * [Vagrant 1.8](https://www.vagrantup.com/docs/installation) 33 | * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 34 | * [Node.js 4](https://nodejs.org/en/download/) 35 | 36 | The following are needed for building and deploying your infrastructure: 37 | 38 | * [Packer 0.9](https://www.packer.io/intro/getting-started/setup.html) 39 | * [Terraform 0.7](https://www.terraform.io/intro/getting-started/install.html) 40 | * AWS account setup ([Sign up](https://aws.amazon.com), [Configure access & local defaults](docs/aws.md)) 41 | 42 | ## Setup 43 | ```bash 44 | $ git clone https://github.com/stphivos/django-angular2-fullstack-devops 45 | $ cd django-angular2-fullstack-devops 46 | 47 | $ vim vars # Edit project variables based on your project 48 | $ ./devops setup # Equivalent to `vagrant up` the first time, except that it destroys and re-creates the machine 49 | $ vagrant ssh # Log into the virtual machine. See all vagrant commands: https://www.vagrantup.com/docs/cli/ 50 | ``` 51 | 52 | ## Run 53 | 54 | ### Frontend 55 | ```bash 56 | $ cd frontend 57 | $ npm start 58 | ``` 59 | [README..](frontend/README.md) 60 | 61 | ### Backend 62 | ```bash 63 | $ cd backend 64 | $ ./manage.py runserver 65 | ``` 66 | [README..](backend/README.md) 67 | 68 | ## Devops 69 | 70 | ### Build 71 | 72 | The first time: 73 | ```bash 74 | $ ./devops build dev # Calls commands below in the order shown 75 | ``` 76 | 77 | For building a specific project, either of the following can be run separately: 78 | ```bash 79 | $ ./devops build dev backend # Uses packer/ansible to provision an amazon machine image (ami) 80 | $ ./devops build dev frontend # Uses gulp to create a target environment distribution 81 | ``` 82 | 83 | ### Deploy 84 | 85 | The first time: 86 | ```bash 87 | $ ./devops deploy dev # Calls commands below in the order shown 88 | ``` 89 | 90 | After updating a specific component, either of the following can be run separately: 91 | ```bash 92 | $ ./devops deploy dev core # Uses terraform to launch shared infrastructure such as vpc/gateway/subnets etc. 93 | $ ./devops deploy dev rds # Uses terraform to launch a db instance on RDS 94 | $ ./devops deploy dev backend # Uses terraform to launch an EC2 instance and load balancer for the backend api 95 | $ ./devops deploy dev frontend # Uses aws-cli to upload the frontend static files to a bucket on S3 96 | ``` 97 | 98 | ### Examples 99 | 100 | Rebuild and deploy frontend for prod environment: 101 | ```bash 102 | $ ./devops pipeline prod frontend # ~30 seconds 103 | ``` 104 | 105 | Rebuild and deploy backend for prod environment: 106 | ```bash 107 | $ ./devops pipeline prod backend # ~7 minutes 108 | ``` 109 | 110 | ## Credits 111 | * Frontend seed - slightly modified version of [mgechev/angular2-seed](https://github.com/mgechev/angular2-seed) 112 | * Ansible playbook for Django - based on [jcalazan/ansible-django-stack](https://github.com/jcalazan/ansible-django-stack) 113 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 7 | config.vm.box = "trusty64" 8 | config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box" 9 | 10 | config.vm.network "private_network", ip: "10.10.10.10" 11 | config.vm.network "forwarded_port", guest: 8000, host: 28000 12 | config.vm.network "forwarded_port", guest: 5432, host: 25432 13 | 14 | config.vm.synced_folder ".", "/home/vagrant/#{ENV['APPLICATION_NAME']}" 15 | 16 | config.vm.provider "virtualbox" do |v| 17 | v.memory = 1024 18 | v.cpus = 2 19 | end 20 | 21 | config.ssh.forward_agent = true 22 | 23 | config.vm.provision "shell", path: "scripts/setup.sh" 24 | config.vm.provision :shell do |s| 25 | s.env = { 26 | SERVER_ROOT_NAME:ENV['SERVER_ROOT_NAME'], 27 | SERVER_ROOT_PATH:ENV['SERVER_ROOT_PATH'], 28 | APPLICATION_NAME:ENV['APPLICATION_NAME'], 29 | APPLICATION_PATH:ENV['APPLICATION_PATH'], 30 | VIRTUALENV_PATH:ENV['VIRTUALENV_PATH'], 31 | GIT_BRANCH:ENV['GIT_BRANCH'], 32 | GIT_REPO:ENV['GIT_REPO'] 33 | } 34 | s.path = "scripts/vagrant.sh" 35 | end 36 | config.vm.provision "ansible" do |ansible| 37 | ansible.playbook = "provisioning/ansible/vagrant.yml" 38 | ansible.verbose = "vv" 39 | #ansible.install = true # For ansible-local provisioner 40 | ansible.limit = "all" 41 | ansible.inventory_path = "provisioning/ansible/inventories/vagrant" 42 | ansible.extra_vars = { 43 | "server_root_name" => "#{ENV['SERVER_ROOT_NAME']}", 44 | "application_name" => "#{ENV['APPLICATION_NAME']}", 45 | "project_name" => "#{ENV['PROJECT_NAME']}", 46 | "virtualenv_path" => "#{ENV['VIRTUALENV_PATH']}", 47 | "project_path" => "#{ENV['PROJECT_PATH']}" 48 | } 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .cache 3 | .coverage 4 | MANIFEST 5 | .tox/ 6 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend 2 | 3 | ## Run server for any IP on port 8000 4 | ```bash 5 | $ ./manage.py runserver 0.0.0.0:8000 6 | ``` 7 | 8 | ## Run tests 9 | ```bash 10 | $ tox 11 | ``` 12 | -------------------------------------------------------------------------------- /backend/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/api/__init__.py -------------------------------------------------------------------------------- /backend/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Profile 4 | 5 | admin.site.register(Profile) 6 | -------------------------------------------------------------------------------- /backend/api/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class ApiConfig(AppConfig): 7 | name = 'api' 8 | -------------------------------------------------------------------------------- /backend/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.5 on 2016-05-09 12:57 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Profile', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('created', models.DateTimeField(auto_now_add=True)), 24 | ('modified', models.DateTimeField(auto_now=True)), 25 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 26 | ], 27 | options={ 28 | 'abstract': False, 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /backend/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/api/migrations/__init__.py -------------------------------------------------------------------------------- /backend/api/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.contrib.auth.models import User 4 | from django.db import models 5 | 6 | 7 | class BaseModel(models.Model): 8 | class Meta: 9 | abstract = True 10 | 11 | def before_insert(self): 12 | pass 13 | 14 | def after_insert(self): 15 | pass 16 | 17 | def before_update(self): 18 | pass 19 | 20 | def after_update(self): 21 | pass 22 | 23 | def before_save(self, is_new): 24 | if is_new: 25 | self.before_insert() 26 | else: 27 | self.before_update() 28 | 29 | def after_save(self, is_new): 30 | if is_new: 31 | self.after_insert() 32 | else: 33 | self.after_update() 34 | 35 | def save(self, **kwargs): 36 | is_new = self.pk is None 37 | 38 | self.before_save(is_new) 39 | 40 | super(BaseModel, self).save(**kwargs) 41 | 42 | self.after_save(is_new) 43 | 44 | 45 | class TrackedModel(BaseModel): 46 | created = models.DateTimeField(auto_now_add=True) 47 | modified = models.DateTimeField(auto_now=True) 48 | 49 | class Meta: 50 | abstract = True 51 | 52 | 53 | class Profile(TrackedModel): 54 | user = models.OneToOneField(User) 55 | 56 | def __unicode__(self): 57 | return self.user.__unicode__() 58 | -------------------------------------------------------------------------------- /backend/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from . import models 4 | 5 | 6 | class ProfileSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = models.Profile 9 | -------------------------------------------------------------------------------- /backend/api/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/api/tests/__init__.py -------------------------------------------------------------------------------- /backend/api/tests/test_profile.py: -------------------------------------------------------------------------------- 1 | from django_mock_queries.asserts import assert_serializer 2 | 3 | from api.models import Profile 4 | from api.serializers import ProfileSerializer 5 | from infrastructure.test_cases import BaseUnitTestCase, BaseIntegrationTestCase 6 | 7 | 8 | class UnitTestProfile(BaseUnitTestCase): 9 | def test_profiles_serializer_returns_expected_field_values(self): 10 | model = self.prepare_model(Profile) 11 | 12 | values = { 13 | 'created': self.format_date(model.created), 14 | 'modified': self.format_date(model.modified), 15 | 'user': model.user_id 16 | } 17 | fields = values.keys() 18 | 19 | assert_serializer(ProfileSerializer) \ 20 | .instance(model) \ 21 | .returns(*fields) \ 22 | .values(**values) \ 23 | .run() 24 | 25 | 26 | class IntegrationTestProfile(BaseIntegrationTestCase): 27 | base_endpoint = '/profiles/' 28 | 29 | def test_profiles_endpoint_forbids_anonymous_users(self): 30 | response = self.pub_api().get(self.base_endpoint) 31 | 32 | self.assert_response(response, 401) 33 | -------------------------------------------------------------------------------- /backend/api/urls.py: -------------------------------------------------------------------------------- 1 | """backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/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.conf.urls import url, include 17 | from rest_framework import routers 18 | 19 | from . import views 20 | 21 | router = routers.DefaultRouter() 22 | router.register(r'profiles', views.ProfileViewSet, base_name='profiles') 23 | 24 | urlpatterns = [ 25 | url(r'^', include(router.urls)), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/api/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from rest_framework import viewsets, permissions 3 | from rest_framework.pagination import LimitOffsetPagination 4 | 5 | from . import serializers, models 6 | 7 | 8 | class AuthenticatedMixin(object): 9 | permission_classes = [permissions.IsAuthenticated] 10 | 11 | 12 | class PaginatedMixin(object): 13 | class CustomPagination(LimitOffsetPagination): 14 | max_limit = settings.REST_FRAMEWORK['MAX_LIMIT'] 15 | 16 | pagination_class = CustomPagination 17 | 18 | 19 | class ProfileViewSet(AuthenticatedMixin, PaginatedMixin, viewsets.ModelViewSet): 20 | queryset = models.Profile.objects.all() 21 | serializer_class = serializers.ProfileSerializer 22 | -------------------------------------------------------------------------------- /backend/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/backend/__init__.py -------------------------------------------------------------------------------- /backend/backend/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from celery import Celery 4 | from django.conf import settings 5 | 6 | app = Celery('backend') 7 | app.config_from_object('django.conf:settings') 8 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 9 | 10 | 11 | @app.task(bind=True) 12 | def debug_task(self): 13 | print('Request: {0!r}'.format(self.request)) 14 | -------------------------------------------------------------------------------- /backend/backend/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/backend/settings/__init__.py -------------------------------------------------------------------------------- /backend/backend/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for backend project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/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 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = 'y_w+5ku5d^i^h_l0%0%#8d#9^&-7$ea+ld@kcm77+ouf=j8#*4' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | # Application definition 30 | 31 | INSTALLED_APPS = [ 32 | 'django.contrib.admin', 33 | 'django.contrib.auth', 34 | 'django.contrib.contenttypes', 35 | 'django.contrib.sessions', 36 | 'django.contrib.messages', 37 | 'django.contrib.staticfiles', 38 | 'oauth2_provider', 39 | 'rest_framework', 40 | 'api', 41 | ] 42 | 43 | MIDDLEWARE_CLASSES = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | AUTHENTICATION_BACKENDS = ( 55 | 'django.contrib.auth.backends.ModelBackend', 56 | ) 57 | 58 | ROOT_URLCONF = 'backend.urls' 59 | 60 | TEMPLATES = [ 61 | { 62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 63 | 'DIRS': [], 64 | 'APP_DIRS': True, 65 | 'OPTIONS': { 66 | 'context_processors': [ 67 | 'django.template.context_processors.debug', 68 | 'django.template.context_processors.request', 69 | 'django.contrib.auth.context_processors.auth', 70 | 'django.contrib.messages.context_processors.messages', 71 | ], 72 | }, 73 | }, 74 | ] 75 | 76 | WSGI_APPLICATION = 'backend.wsgi.application' 77 | 78 | # Database 79 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 80 | 81 | DATABASES = { 82 | 'default': { 83 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 84 | 'NAME': os.getenv('DATABASE_NAME'), 85 | 'USER': os.getenv('DATABASE_USER'), 86 | 'PASSWORD': os.getenv('DATABASE_PASSWORD'), 87 | 'HOST': os.getenv('DATABASE_HOST'), 88 | 'PORT': os.getenv('DATABASE_PORT'), # Empty string for default (5432) 89 | } 90 | } 91 | 92 | # Password validation 93 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 94 | 95 | AUTH_PASSWORD_VALIDATORS = [ 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 107 | }, 108 | ] 109 | 110 | # Internationalization 111 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 112 | 113 | LANGUAGE_CODE = 'en-us' 114 | 115 | TIME_ZONE = 'UTC' 116 | 117 | USE_I18N = True 118 | 119 | USE_L10N = True 120 | 121 | USE_TZ = True 122 | 123 | # Static files (CSS, JavaScript, Images) 124 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 125 | 126 | # URL to use when referring to static files located in STATIC_ROOT 127 | STATIC_URL = '/static/' 128 | 129 | # Where to look for static files 130 | STATICFILES_DIRS = [ 131 | os.path.join(BASE_DIR, '../static/src'), 132 | ] 133 | 134 | # The absolute path to the directory where collectstatic will collect static files for deployment 135 | STATIC_ROOT = os.path.join(BASE_DIR, '../static/collect/') 136 | 137 | REST_FRAMEWORK = { 138 | 'PAGE_SIZE': 10, 139 | 'MAX_LIMIT': 100, 140 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 141 | 'oauth2_provider.ext.rest_framework.OAuth2Authentication', 142 | 'rest_framework.authentication.SessionAuthentication', 143 | ) 144 | } 145 | 146 | MOMMY_CUSTOM_FIELDS_GEN = { 147 | 'django.db.models.fields.NullBooleanField': lambda: None 148 | } 149 | -------------------------------------------------------------------------------- /backend/backend/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | -------------------------------------------------------------------------------- /backend/backend/settings/local.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | -------------------------------------------------------------------------------- /backend/backend/settings/prod.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | -------------------------------------------------------------------------------- /backend/backend/urls.py: -------------------------------------------------------------------------------- 1 | """backend URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/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.conf.urls import url, include 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), 22 | url(r'^oauth/', include('oauth2_provider.urls', namespace='oauth2_provider')), 23 | url(r'', include('api.urls')), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend 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.9/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", "backend.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/db.sqlite3 -------------------------------------------------------------------------------- /backend/infrastructure/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/infrastructure/__init__.py -------------------------------------------------------------------------------- /backend/infrastructure/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class InfrastructureConfig(AppConfig): 7 | name = 'infrastructure' 8 | -------------------------------------------------------------------------------- /backend/infrastructure/test_cases.py: -------------------------------------------------------------------------------- 1 | import json 2 | from django.contrib.auth.models import User 3 | from django.test import TestCase 4 | from model_mommy import mommy 5 | from model_mommy.generators import gen_string, gen_integer 6 | from rest_framework import fields as drf_fields 7 | from rest_framework.test import APIClient 8 | from urllib import urlencode 9 | 10 | from api import models 11 | 12 | 13 | class BaseUnitTestCase(TestCase): 14 | def prepare_model(self, cls, identity=True, optional=True, **attrs): 15 | default = dict( 16 | _fill_optional=optional 17 | ) 18 | if identity: 19 | default.update(id=gen_integer(min_int=1)) 20 | return mommy.prepare(cls, **dict(default, **attrs)) 21 | 22 | def format_date(self, value): 23 | return drf_fields.DateTimeField().to_representation(value) 24 | 25 | 26 | class BaseIntegrationTestCase(BaseUnitTestCase): 27 | route = None 28 | 29 | def setUp(self): 30 | super(BaseIntegrationTestCase, self).setUp() 31 | 32 | self.client = APIClient() 33 | self.client.default_format = 'json' 34 | 35 | self.password = gen_string(8) 36 | self.user = self.create_user(password=self.password) 37 | self.user.profile = self.create_model(models.Profile) 38 | 39 | def tearDown(self): 40 | super(BaseIntegrationTestCase, self).tearDown() 41 | 42 | @property 43 | def base_endpoint(self): 44 | return '/api/{0}/'.format(self.route) 45 | 46 | def user_api(self, user=None): 47 | user = self.user if not user else user 48 | self.client.force_authenticate(user=user, token=user.token if hasattr(user, 'token') else None) 49 | 50 | return self.client 51 | 52 | def pub_api(self): 53 | self.client.defaults.update({'HTTP_USER_AGENT': 'Tests'}) 54 | self.client.force_authenticate(user=None) 55 | 56 | return self.client 57 | 58 | def get_endpoint(self, base_path, *relative_paths, **query): 59 | target_path = base_path + '/'.join([str(x) for x in relative_paths]) + ('/' if relative_paths else '') 60 | if 'format' not in query: 61 | query.update({'format': 'json'}) 62 | return target_path + '?' + urlencode(query) 63 | 64 | def assert_response(self, response, code): 65 | query = ('?' + response.request['QUERY_STRING']) if response.request['QUERY_STRING'] else '' 66 | self.assertEqual( 67 | response.status_code, 68 | code, 69 | '{0} {1} produced {2} instead of {3} => {4}'.format( 70 | response.request['REQUEST_METHOD'], 71 | response.request['PATH_INFO'] + query, 72 | response.status_code, 73 | code, 74 | response.content 75 | ) 76 | ) 77 | 78 | def get_json(self, response): 79 | return json.loads(response.content) 80 | 81 | def create_model(self, cls, optional=True, **attrs): 82 | return mommy.make(cls, _fill_optional=optional, **attrs) 83 | 84 | def create_user(self, **kwargs): 85 | defaults = dict( 86 | is_active=kwargs.get('is_active', True), 87 | username=kwargs.get('username', gen_string(10)), 88 | email=kwargs.get('email', gen_string(10) + '@localhost') 89 | ) 90 | 91 | user = self.prepare_model(User, identity=False, **dict(defaults, **kwargs)) 92 | user.set_password(kwargs.get('password', gen_string(8))) 93 | user.save() 94 | 95 | return user 96 | 97 | def create_profile(self, **kwargs): 98 | defaults = dict( 99 | user=kwargs.get('user', self.create_user()), 100 | ) 101 | return self.create_model(models.Profile, **dict(defaults, **kwargs)) 102 | -------------------------------------------------------------------------------- /backend/infrastructure/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stphivos/django-angular2-fullstack-devops/32ca98ecab1a5918492dc13066bcfb377068790b/backend/infrastructure/tests/__init__.py -------------------------------------------------------------------------------- /backend/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", "backend.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /backend/requirements/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | pytest==2.9.1 4 | pytest-django==2.9.1 5 | pytest-xdist==1.14 6 | pytest-cov==2.2.1 7 | pytest-flake8==0.5 8 | coverage==3.7.1 9 | tox==2.1.1 10 | virtualenv==13.1.2 11 | mock==1.3.0 12 | model_mommy==1.2.6 13 | django_mock_queries==0.0.13 14 | -------------------------------------------------------------------------------- /backend/requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.9.5 2 | psycopg2==2.6.1 3 | djangorestframework==3.3.3 4 | django-oauth-toolkit==0.10.0 5 | celery==3.1.18 6 | -------------------------------------------------------------------------------- /backend/static/collect/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /backend/static/src/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /backend/tox.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | flake8-max-line-length = 120 3 | flake8-ignore = 4 | apps.py ALL 5 | __init__.py ALL 6 | norecursedirs = migrations management 7 | DJANGO_SETTINGS_MODULE = backend.settings.local 8 | 9 | [flake8] 10 | max-line-length = 120 11 | exclude = */migrations/* 12 | max-complexity = 10 13 | 14 | [tox] 15 | envlist = py27 16 | skipsdist = True 17 | 18 | [testenv] 19 | deps = -rrequirements/requirements-dev.txt 20 | commands = 21 | py.test api/tests/ --cov=. --cov-report= 22 | flake8 api 23 | coverage report -m --include=api/* --omit=*/tests/*,*/migrations/*,*/management/*,*/apps.py,*/__init__.py,*test*.py 24 | -------------------------------------------------------------------------------- /docs/aws.md: -------------------------------------------------------------------------------- 1 | # AWS Settings 2 | 3 | ## Credentials 4 | Set your default AWS access key id and secret key: 5 | ```bash 6 | $ vim ~/.aws/credentials 7 | ``` 8 | 9 | Paste credentials and save: 10 | ``` 11 | [default] 12 | aws_access_key_id=AKIAIOSFODNN7EXAMPLE 13 | aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 14 | ``` 15 | 16 | ## Config 17 | Set your default aws cli region/output format: 18 | ```bash 19 | $ vim ~/.aws/config 20 | ``` 21 | 22 | Paste settings and save: 23 | ``` 24 | [default] 25 | region=eu-central-1 26 | output=json 27 | ``` 28 | 29 | Alternatively you can set all the above interactively using: 30 | ```bash 31 | $ aws configure 32 | AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE 33 | AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 34 | Default region name [None]: us-west-2 35 | Default output format [None]: json 36 | ``` 37 | 38 | ## SSH key pair 39 | 40 | Create a new key pair for the project: 41 | ```bash 42 | $ ssh-keygen -t rsa -f ~/.ssh/fullstack 43 | ``` 44 | 45 | If you prefer to use your default `id_rsa` key pair make sure to update key `SSH_KEY_NAME` in the `vars` file. 46 | 47 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | /node_modules/ 26 | /typings/ 27 | 28 | # Users Environment Variables 29 | .lock-wscript 30 | .tsdrc 31 | .typingsrc 32 | 33 | #IDE configuration files 34 | .idea 35 | .vscode 36 | *.iml 37 | 38 | /tools/**/*.js 39 | dist 40 | dev 41 | docs 42 | lib 43 | test 44 | tmp 45 | 46 | gulpfile.js 47 | gulpfile.js.map 48 | 49 | -------------------------------------------------------------------------------- /frontend/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 140, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": false, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false, 59 | "ng": false 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /frontend/.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "block-no-empty": null, 5 | "at-rule-empty-line-before": null, 6 | "rule-non-nested-empty-line-before": null 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | - 5 5 | - 6 6 | 7 | sudo: false 8 | 9 | before_install: 10 | - export CHROME_BIN=chromium-browser # Karma CI 11 | - export DISPLAY=:99.0 12 | 13 | before_script: 14 | - sh -e /etc/init.d/xvfb start 15 | - nohup bash -c webdriver-manager start 2>&1 & # Protractor CI 16 | - sleep 1 # give server time to start 17 | 18 | after_failure: 19 | - cat /home/travis/build/mgechev/angular2-seed/npm-debug.log 20 | 21 | branches: 22 | only: master 23 | 24 | notifications: 25 | email: true 26 | webhooks: 27 | urls: https://webhooks.gitter.im/e/565e4b2fed3b96c1b964 28 | on_success: change # options: [always|never|change] default: always 29 | on_failure: always # options: [always|never|change] default: always 30 | on_start: never # options: [always|never|change] default: always 31 | 32 | env: 33 | global: 34 | # https://github.com/DefinitelyTyped/tsd#tsdrc 35 | # Token has no scope (read-only access to public information) 36 | - TSD_GITHUB_TOKEN=9b18c72997769f3867ef2ec470e626d39661795d 37 | 38 | cache: 39 | directories: node_modules 40 | 41 | script: 42 | - npm run tests.all 43 | -------------------------------------------------------------------------------- /frontend/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Minko Gechev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor file 2 | # http://www.appveyor.com/docs/appveyor-yml 3 | # This file: cloned from https://github.com/gruntjs/grunt/blob/master/appveyor.yml 4 | 5 | # Build version format 6 | version: "{build}" 7 | 8 | # Test against this version of Node.js 9 | environment: 10 | nodejs_version: "Stable" 11 | # https://github.com/DefinitelyTyped/tsd#tsdrc 12 | # Token has no scope (read-only access to public information) 13 | TSD_GITHUB_TOKEN: "9b18c72997769f3867ef2ec470e626d39661795d" 14 | 15 | build: off 16 | 17 | clone_depth: 10 18 | 19 | # Fix line endings on Windows 20 | init: 21 | - git config --global core.autocrlf true 22 | 23 | install: 24 | - ps: Install-Product node $env:nodejs_version 25 | - npm install -g npm 26 | - ps: $env:path = $env:appdata + "\npm;" + $env:path 27 | - npm install 28 | 29 | test_script: 30 | # Output useful info for debugging. 31 | - node --version && npm --version 32 | # We test multiple Windows shells because of prior stdout buffering issues 33 | # filed against Grunt. https://github.com/joyent/node/issues/3584 34 | - ps: "npm --version # PowerShell" # Pass comment to PS for easier debugging 35 | - npm run tests.all 36 | 37 | notifications: 38 | - provider: Webhook 39 | url: https://webhooks.gitter.im/e/cfd8ce5ddee6f3a0b0c9 40 | on_build_success: false 41 | on_build_failure: true 42 | on_build_status_changed: true 43 | 44 | cache: node_modules 45 | -------------------------------------------------------------------------------- /frontend/gulpfile.ts: -------------------------------------------------------------------------------- 1 | import * as gulp from 'gulp'; 2 | import * as runSequence from 'run-sequence'; 3 | 4 | import { PROJECT_TASKS_DIR, SEED_TASKS_DIR } from './tools/config'; 5 | import { loadTasks } from './tools/utils'; 6 | 7 | 8 | loadTasks(SEED_TASKS_DIR); 9 | loadTasks(PROJECT_TASKS_DIR); 10 | 11 | 12 | // -------------- 13 | // Build dev. 14 | gulp.task('build.dev', (done: any) => 15 | runSequence(//'clean.dev', 16 | // 'tslint', 17 | // 'css-lint', 18 | 'build.assets.dev', 19 | 'build.html_css', 20 | 'build.js.dev', 21 | 'build.index.dev', 22 | done)); 23 | 24 | // -------------- 25 | // Build dev watch. 26 | gulp.task('build.dev.watch', (done: any) => 27 | runSequence('build.dev', 28 | 'watch.dev', 29 | done)); 30 | 31 | // -------------- 32 | // Build e2e. 33 | gulp.task('build.e2e', (done: any) => 34 | runSequence('clean.dev', 35 | 'tslint', 36 | 'build.assets.dev', 37 | 'build.js.e2e', 38 | 'build.index.dev', 39 | done)); 40 | 41 | // -------------- 42 | // Build prod. 43 | gulp.task('build.prod', (done: any) => 44 | runSequence('clean.prod', 45 | 'tslint', 46 | 'css-lint', 47 | 'build.assets.prod', 48 | 'build.html_css', 49 | 'copy.js.prod', 50 | 'build.js.prod', 51 | 'build.bundles', 52 | 'build.bundles.app', 53 | 'build.index.prod', 54 | done)); 55 | 56 | // -------------- 57 | // Build test. 58 | gulp.task('build.test', (done: any) => 59 | runSequence('clean.dev', 60 | 'tslint', 61 | 'build.assets.dev', 62 | 'build.js.test', 63 | 'build.index.dev', 64 | done)); 65 | 66 | // -------------- 67 | // Build test watch. 68 | gulp.task('build.test.watch', (done: any) => 69 | runSequence('build.test', 70 | 'watch.test', 71 | done)); 72 | 73 | // -------------- 74 | // Build tools. 75 | gulp.task('build.tools', (done: any) => 76 | runSequence('clean.tools', 77 | 'build.js.tools', 78 | done)); 79 | 80 | // -------------- 81 | // Docs 82 | // gulp.task('docs', (done: any) => 83 | // runSequence('build.docs', 84 | // 'serve.docs', 85 | // done)); 86 | 87 | // -------------- 88 | // Serve dev 89 | gulp.task('serve.dev', (done: any) => 90 | runSequence('build.dev', 91 | 'server.start', 92 | 'watch.dev', 93 | done)); 94 | 95 | // -------------- 96 | // Serve e2e 97 | gulp.task('serve.e2e', (done: any) => 98 | runSequence('build.e2e', 99 | 'server.start', 100 | 'watch.e2e', 101 | done)); 102 | 103 | 104 | // -------------- 105 | // Serve prod 106 | gulp.task('serve.prod', (done: any) => 107 | runSequence('build.prod', 108 | 'server.prod', 109 | done)); 110 | 111 | 112 | // -------------- 113 | // Test. 114 | gulp.task('test', (done: any) => 115 | runSequence('build.test', 116 | 'karma.start', 117 | done)); 118 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Jul 15 2015 09:44:02 GMT+0200 (Romance Daylight Time) 3 | 'use strict'; 4 | 5 | var argv = require('yargs').argv; 6 | 7 | module.exports = function(config) { 8 | config.set({ 9 | 10 | // base path that will be used to resolve all patterns (eg. files, exclude) 11 | basePath: './', 12 | 13 | 14 | // frameworks to use 15 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 16 | frameworks: ['jasmine'], 17 | 18 | 19 | // list of files / patterns to load in the browser 20 | files: [ 21 | // Polyfills. 22 | 'node_modules/es6-shim/es6-shim.js', 23 | 24 | 'node_modules/reflect-metadata/Reflect.js', 25 | 26 | // System.js for module loading 27 | 'node_modules/systemjs/dist/system-polyfills.js', 28 | 'node_modules/systemjs/dist/system.src.js', 29 | 30 | // Zone.js dependencies 31 | 'node_modules/zone.js/dist/zone.js', 32 | 'node_modules/zone.js/dist/jasmine-patch.js', 33 | 'node_modules/zone.js/dist/async-test.js', 34 | 'node_modules/zone.js/dist/fake-async-test.js', 35 | 36 | // RxJs. 37 | { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, 38 | { pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, 39 | 40 | // paths loaded via module imports 41 | // Angular itself 42 | { pattern: 'node_modules/@angular/**/*.js', included: false, watched: true }, 43 | 44 | { pattern: 'dist/dev/**/*.js', included: false, watched: true }, 45 | { pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: false, watched: false }, // PhantomJS2 (and possibly others) might require it 46 | 47 | // suppress annoying 404 warnings for resources, images, etc. 48 | { pattern: 'dist/dev/assets/**/*', watched: false, included: false, served: true }, 49 | 50 | 'test-main.js' 51 | ], 52 | 53 | // must go along with above, suppress annoying 404 warnings. 54 | proxies: { 55 | '/assets/': '/base/dist/dev/assets/' 56 | }, 57 | 58 | // list of files to exclude 59 | exclude: [ 60 | ], 61 | 62 | 63 | // preprocess matching files before serving them to the browser 64 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 65 | preprocessors: { 66 | 'dist/**/!(*spec).js': ['coverage'] 67 | }, 68 | 69 | // test results reporter to use 70 | // possible values: 'dots', 'progress' 71 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 72 | reporters: ['mocha', 'coverage'], 73 | 74 | 75 | // web server port 76 | port: 9876, 77 | 78 | 79 | // enable / disable colors in the output (reporters and logs) 80 | colors: true, 81 | 82 | 83 | // level of logging 84 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 85 | logLevel: config.LOG_INFO, 86 | 87 | 88 | // enable / disable watching file and executing tests whenever any file changes 89 | autoWatch: true, 90 | 91 | 92 | // start these browsers 93 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 94 | browsers: [ 95 | 'PhantomJS', 96 | 'Chrome' 97 | ], 98 | 99 | 100 | customLaunchers: { 101 | Chrome_travis_ci: { 102 | base: 'Chrome', 103 | flags: ['--no-sandbox'] 104 | } 105 | }, 106 | 107 | coverageReporter: { 108 | dir: 'coverage/', 109 | reporters: [ 110 | { type: 'text-summary' }, 111 | { type: 'json', subdir: '.', file: 'coverage-final.json' }, 112 | { type: 'html' } 113 | ] 114 | }, 115 | 116 | // Continuous Integration mode 117 | // if true, Karma captures browsers, runs the tests and exits 118 | singleRun: false, 119 | 120 | // Passing command line arguments to tests 121 | client: { 122 | files: argv.files 123 | } 124 | }); 125 | 126 | if (process.env.APPVEYOR) { 127 | config.browsers = ['IE']; 128 | config.singleRun = true; 129 | config.browserNoActivityTimeout = 90000; // Note: default value (10000) is not enough 130 | } 131 | 132 | if (process.env.TRAVIS || process.env.CIRCLECI) { 133 | config.browsers = ['Chrome_travis_ci']; 134 | config.singleRun = true; 135 | } 136 | }; 137 | -------------------------------------------------------------------------------- /frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | baseUrl: 'http://localhost:5555/', 3 | 4 | specs: [ 5 | './dist/dev/**/*.e2e-spec.js' 6 | ], 7 | 8 | exclude: [], 9 | 10 | // 'jasmine' by default will use the latest jasmine framework 11 | framework: 'jasmine', 12 | 13 | // allScriptsTimeout: 110000, 14 | 15 | jasmineNodeOpts: { 16 | // showTiming: true, 17 | showColors: true, 18 | isVerbose: false, 19 | includeStackTrace: false, 20 | // defaultTimeoutInterval: 400000 21 | }, 22 | 23 | directConnect: true, 24 | 25 | capabilities: { 26 | browserName: 'chrome' 27 | }, 28 | 29 | onPrepare: function() { 30 | const SpecReporter = require('jasmine-spec-reporter'); 31 | // add jasmine spec reporter 32 | jasmine.getEnv().addReporter(new SpecReporter({ displayStacktrace: true })); 33 | 34 | browser.ignoreSynchronization = false; 35 | }, 36 | 37 | 38 | /** 39 | * Angular 2 configuration 40 | * 41 | * useAllAngular2AppRoots: tells Protractor to wait for any angular2 apps on the page instead of just the one matching 42 | * `rootEl` 43 | */ 44 | useAllAngular2AppRoots: true 45 | }; 46 | 47 | if (process.env.TRAVIS) { 48 | config.capabilities = { 49 | browserName: 'firefox' 50 | }; 51 | } 52 | 53 | exports.config = config; 54 | -------------------------------------------------------------------------------- /frontend/src/client/app/+about/about.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 0 16px; 4 | } 5 | 6 | h2 { 7 | font-size: 20px; 8 | font-weight: 500; 9 | letter-spacing: 0.005em; 10 | margin-bottom: 0; 11 | margin-top: 0.83em; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/client/app/+about/about.component.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | describe('About', () => { 2 | beforeEach(() => { 3 | browser.get('/'); 4 | element.all(by.css('nav > a')).get(1).click(); 5 | }); 6 | 7 | it('should have correct feature heading', () => { 8 | let el = element(by.css('sd-about h2')); 9 | expect(el.getText()).toEqual('Features'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/src/client/app/+about/about.component.html: -------------------------------------------------------------------------------- 1 |

2 | Angular 2 Seed is a starter project that implements best practices in 3 | coding, building and testing Angular 2 apps. 4 |

5 | 6 |

Features

7 | 16 | -------------------------------------------------------------------------------- /frontend/src/client/app/+about/about.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestComponentBuilder } from '@angular/compiler/testing'; 2 | import { Component } from '@angular/core'; 3 | import { 4 | describe, 5 | expect, 6 | inject, 7 | it 8 | } from '@angular/core/testing'; 9 | import { getDOM } from '@angular/platform-browser/src/dom/dom_adapter'; 10 | 11 | import { AboutComponent } from './about.component'; 12 | 13 | export function main() { 14 | describe('About component', () => { 15 | 16 | 17 | it('should work', 18 | inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { 19 | tcb.createAsync(TestComponent) 20 | .then((rootTC: any) => { 21 | let aboutDOMEl = rootTC.debugElement.children[0].nativeElement; 22 | 23 | expect(getDOM().querySelectorAll(aboutDOMEl, 'h2')[0].textContent).toEqual('Features'); 24 | }); 25 | })); 26 | }); 27 | } 28 | 29 | @Component({ 30 | selector: 'test-cmp', 31 | directives: [AboutComponent], 32 | template: '' 33 | }) 34 | class TestComponent {} 35 | -------------------------------------------------------------------------------- /frontend/src/client/app/+about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'sd-about', 5 | templateUrl: 'app/+about/about.component.html', 6 | styleUrls: ['app/+about/about.component.css'] 7 | }) 8 | /** 9 | * This class represents the lazy loaded AboutComponent. 10 | */ 11 | export class AboutComponent {} 12 | -------------------------------------------------------------------------------- /frontend/src/client/app/+about/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded 3 | * AboutComponent. 4 | */ 5 | export * from './about.component'; 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/client/app/+home/home.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 0 16px; 4 | } 5 | 6 | input { 7 | width: 250px; 8 | } 9 | 10 | ul { 11 | list-style-type: none; 12 | padding: 0 0 0 8px; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/client/app/+home/home.component.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | describe('Home', () => { 2 | 3 | beforeEach( () => { 4 | browser.get('/'); 5 | }); 6 | 7 | it('should have an input', () => { 8 | expect(element(by.css('sd-app sd-home form input')).isPresent()).toEqual(true); 9 | }); 10 | 11 | it('should have a list of computer scientists', () => { 12 | expect(element(by.css('sd-app sd-home ul')).getText()) 13 | .toEqual('Edsger Dijkstra\nDonald Knuth\nAlan Turing\nGrace Hopper'); 14 | }); 15 | 16 | it('should add a name to the list using the form', () => { 17 | element(by.css('sd-app sd-home form input')).sendKeys('Tim Berners-Lee'); 18 | element(by.css('sd-app sd-home form button')).click(); 19 | expect(element(by.css('sd-app sd-home ul')).getText()) 20 | .toEqual('Edsger Dijkstra\nDonald Knuth\nAlan Turing\nGrace Hopper\nTim Berners-Lee'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /frontend/src/client/app/+home/home.component.html: -------------------------------------------------------------------------------- 1 |

2 | Howdy! Here's a list of awesome computer scientists. 3 | Do you know any others? Add to the list yourself. 4 |

5 | 6 |
7 | 8 | 9 |
10 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /frontend/src/client/app/+home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component, provide } from '@angular/core'; 2 | import { TestComponentBuilder } from '@angular/compiler/testing'; 3 | import { 4 | describe, 5 | expect, 6 | inject, 7 | it 8 | } from '@angular/core/testing'; 9 | import { 10 | BaseRequestOptions, 11 | ConnectionBackend, 12 | Http, 13 | HTTP_PROVIDERS 14 | } from '@angular/http'; 15 | import { MockBackend } from '@angular/http/testing'; 16 | import { getDOM } from '@angular/platform-browser/src/dom/dom_adapter'; 17 | 18 | import { NameListService } from '../shared/index'; 19 | import { HomeComponent } from './home.component'; 20 | 21 | export function main() { 22 | describe('Home component', () => { 23 | it('should work', 24 | inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { 25 | tcb.createAsync(TestComponent) 26 | .then((rootTC: any) => { 27 | rootTC.detectChanges(); 28 | 29 | let homeInstance = rootTC.debugElement.children[0].componentInstance; 30 | let homeDOMEl = rootTC.debugElement.children[0].nativeElement; 31 | 32 | expect(homeInstance.nameListService).toEqual(jasmine.any(NameListService)); 33 | expect(getDOM().querySelectorAll(homeDOMEl, 'li').length).toEqual(0); 34 | 35 | homeInstance.newName = 'Minko'; 36 | homeInstance.addName(); 37 | rootTC.detectChanges(); 38 | 39 | expect(getDOM().querySelectorAll(homeDOMEl, 'li').length).toEqual(1); 40 | 41 | expect(getDOM().querySelectorAll(homeDOMEl, 'li')[0].textContent).toEqual('Minko'); 42 | }); 43 | })); 44 | }); 45 | } 46 | 47 | @Component({ 48 | providers: [ 49 | HTTP_PROVIDERS, 50 | NameListService, 51 | BaseRequestOptions, 52 | MockBackend, 53 | provide(Http, { 54 | useFactory: function(backend: ConnectionBackend, defaultOptions: BaseRequestOptions) { 55 | return new Http(backend, defaultOptions); 56 | }, 57 | deps: [MockBackend, BaseRequestOptions] 58 | }), 59 | ], 60 | selector: 'test-cmp', 61 | template: '', 62 | directives: [HomeComponent] 63 | }) 64 | class TestComponent {} 65 | -------------------------------------------------------------------------------- /frontend/src/client/app/+home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { FORM_DIRECTIVES } from '@angular/common'; 2 | import { Component } from '@angular/core'; 3 | 4 | import { NameListService } from '../shared/index'; 5 | 6 | @Component({ 7 | selector: 'sd-home', 8 | templateUrl: 'app/+home/home.component.html', 9 | styleUrls: ['app/+home/home.component.css'], 10 | directives: [FORM_DIRECTIVES] 11 | }) 12 | /** 13 | * This class represents the lazy loaded HomeComponent. 14 | */ 15 | export class HomeComponent { 16 | 17 | newName: string; 18 | 19 | /** 20 | * Creates an instance of the HomeComponent with the injected 21 | * NameListService. 22 | * 23 | * @param {NameListService} nameListService the injected NameListService 24 | */ 25 | constructor(public nameListService: NameListService) {} 26 | 27 | /** 28 | * Calls the add method of the NameListService with the current 29 | * newName value of the form. 30 | * @return {boolean} false to prevent default form submit behavior to refresh the page. 31 | */ 32 | addName(): boolean { 33 | this.nameListService.add(this.newName); 34 | this.newName = ''; 35 | return false; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/client/app/+home/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This barrel file provides the export for the lazy loaded 3 | * HomeComponent. 4 | */ 5 | export * from './home.component'; 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/client/app/app.component.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | describe('App', () => { 2 | 3 | beforeEach( () => { 4 | browser.get('/'); 5 | }); 6 | 7 | it('should have a title', () => { 8 | expect(browser.getTitle()).toEqual('My Angular2 App'); 9 | }); 10 | 11 | it('should have