├── .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 |
8 | - Ready to go, statically typed build system using Gulp for working with TypeScript.
9 | - Production and development builds.
10 | - Sample unit tests with Jasmine and Karma including code coverage via Istanbul.
11 | - End-to-end tests with Protractor.
12 | - Development server with live reload.
13 | - TypeScript definition management using Typings.
14 | - Basic Service Worker, which implements "Cache then network strategy".
15 |
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 |
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