Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quickstart #1

Open
wants to merge 13 commits into
base: fork
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .buildpacks
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
api=https://github.com/heroku/heroku-buildpack-php.git#v148
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,6 @@ indent_size = 2
[phpunit.xml{,.dist}]
indent_style = space
indent_size = 4

[Makefile]
indent_style = tab
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# Docker
/docker-compose.override.yaml
/docker-compose.override.yml

# IDEs
.idea/
.code/

# Makefile targets
provision
178 changes: 178 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#=============================================================================#
# JWT CONFIGURATION
#=============================================================================#
ifndef EMAIL
[email protected]
endif
ifndef PASSWORD
PASSWORD=123456
endif

ifndef JWT_DIR
JWT_DIR=api/config/jwt
endif
JWT_PRIVATE_KEY=${JWT_DIR}/private.pem
JWT_PUBLIC_KEY=${JWT_DIR}/public.pem

ifndef JWT_PASSPHRASE
JWT_PASSPHRASE=!ChangeMe!
endif

${JWT_DIR}:
mkdir -p ${JWT_DIR}

${JWT_PRIVATE_KEY}: ${JWT_DIR}
openssl genrsa -passout pass:${JWT_PASSPHRASE} -out $@ -aes256 4096

${JWT_PUBLIC_KEY}: ${JWT_PRIVATE_KEY}
openssl rsa -passin pass:${JWT_PASSPHRASE} -pubout -in $< -out $@

api/.env.local: ${JWT_PRIVATE_KEY} ${JWT_PUBLIC_KEY}
rm -f $@
echo JWT_PASSPHRASE=$(JWT_PASSPHRASE) >> $@
echo JWT_PRIVATE_KEY=`cat ${JWT_PRIVATE_KEY} | base64 | tr -d '\n'` >> $@
echo JWT_PUBLIC_KEY=`cat ${JWT_PUBLIC_KEY} | base64 | tr -d '\n'` >> $@
echo JWT_PRIVATE_KEY_FILE=%kernel.project_dir%/config/jwt/private.pem >> $@
echo JWT_PUBLIC_KEY_FILE=%kernel.project_dir%/config/jwt/public.pem >> $@

clean/jwt:
rm -rf $(JWT_DIR)


#=============================================================================#
# INIT DEPLOYMENT TARGETS
#=============================================================================#
ifndef DEPLOYMENT_ENV
DEPLOYMENT_ENV=staging
endif
ifndef HEROKU_TEAM
HEROKU_TEAM=YOUR_TEAM
endif
ifndef HEROKU_REGION
HEROKU_REGION=eu
endif
ifndef AWS_REGION
AWS_REGION=eu-west-3
endif

API_APP_NAME=$(HEROKU_TEAM)-api-$(DEPLOYMENT_ENV)
API_HOST=$(HEROKU_TEAM)-api-$(DEPLOYMENT_ENV).herokuapp.com
API_URL=https://$(API_HOST)

ADMIN_APP_NAME=$(HEROKU_TEAM)-admin-$(DEPLOYMENT_ENV)
ADMIN_BUCKET_URL=s3://$(ADMIN_APP_NAME)
ADMIN_URL=http://$(ADMIN_APP_NAME).s3-website.$(AWS_REGION).amazonaws.com

provision/api/app:
heroku apps:create \
--team $(HEROKU_TEAM) \
--region $(HEROKU_REGION) \
--remote $(DEPLOYMENT_ENV) \
--buildpack https://github.com/negativetwelve/heroku-buildpack-subdir \
--addons heroku-postgresql:hobby-dev \
$(API_APP_NAME)
mkdir -p $@

provision/api/environment: provision/api/app ${JWT_PUBLIC_KEY}
heroku config:set -a $(API_APP_NAME) \
APP_ENV=prod \
APP_SECRET=$(shell openssl rand -base64 32) \
CORS_ALLOW_ORIGIN=$(ADMIN_URL) \
JWT_PASSPHRASE=$(JWT_PASSPHRASE) \
JWT_PRIVATE_KEY=`cat ${JWT_PRIVATE_KEY} | base64 | tr -d '\n'` \
JWT_PUBLIC_KEY=`cat ${JWT_PUBLIC_KEY} | base64 | tr -d '\n'` \
TRUSTED_HOSTS=$(API_HOST)
mkdir -p $@

provision/api: provision/api/environment

provision/admin:
aws s3 mb --region $(AWS_REGION) s3://$(HEROKU_TEAM)-admin-staging
aws s3 website $(ADMIN_BUCKET_URL) --index-document index.html --error-document index.html
mkdir -p $@

provision: provision/api provision/admin

clean/provision:
rm -rf provision

destroy/api:
heroku apps:destroy -a $(API_APP_NAME)

destroy/admin:
aws s3 rb $(ADMIN_BUCKET_URL) $(FORCE)

destroy: destroy/api destroy/admin


#=============================================================================#
# DEPLOYMENT TARGETS
#=============================================================================#
deploy/api:
git push $(FORCE) $(DEPLOYMENT_ENV) HEAD:master

api/user:
heroku run -r $(DEPLOYMENT_ENV) ./api/bin/console fos:user:create

api/token:
curl -X POST \
-H "Content-Type: application/json" $(API_URL)/login_check \
-d '{"login":"$(EMAIL)","password":"$(PASSWORD)"}' 2>/dev/null

admin/build: install
echo REACT_APP_API_ENTRYPOINT=$(API_URL) >> admin/.env.production.local
docker-compose run admin /bin/sh -c 'yarn build'

deploy/admin: admin/build
aws s3 sync $< $(ADMIN_BUCKET_URL) --acl public-read
# aws cloudfront create-invalidation --distribution-id $(AWS_CLOUDFRONT_DISTRIBUTION_ID) --paths "/*"

deploy: deploy/api deploy/admin

clean/deploy:
rm -f admin/.env.production.local
rm -rf admin/build


#=============================================================================#
# DOCKER TARGETS
#=============================================================================#
clean/docker/volumes:
docker system prune --volumes

clean/docker:
docker system prune --all --force --volumes


#=============================================================================#
# GENERAL TARGETS
#=============================================================================#
update:
# update-deps.sh

test:
@echo "TODO test"

install: api/.env.local

start: test install
docker-compose up -d

fixtures: start
docker-compose exec php ./bin/console doctrine:fixtures:load

token:
curl -X POST \
-H "Content-Type: application/json" http://localhost:8080/login_check \
-d '{"login":"$(EMAIL)","password":"$(PASSWORD)"}'

stop:
docker-compose down

clean: stop clean/deploy clean/jwt clean/docker/volumes

clean/all: clean clean/provision clean/docker

all: start

.DEFAULT_GOAL := start
2 changes: 2 additions & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
release: ./api/bin/console doctrine:migrations:migrate --no-interaction
web: pushd api && $(composer config bin-dir)/heroku-php-apache2 public/
54 changes: 54 additions & 0 deletions admin/src/Admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import parseHydraDocumentation from '@api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
import {
HydraAdmin,
hydraClient,
fetchHydra as baseFetchHydra,
} from '@api-platform/admin';
import authProvider from './authProvider';
import { Redirect } from 'react-router-dom';

const entrypoint = process.env.REACT_APP_API_ENTRYPOINT;
const fetchHeaders = {
Authorization: `Bearer ${window.localStorage.getItem('token')}`,
};
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
const dataProvider = api => hydraClient(api, fetchHydra);
const apiDocumentationParser = entrypoint =>
parseHydraDocumentation(entrypoint, {
headers: new Headers(fetchHeaders),
}).then(
({ api }) => ({ api }),
result => {
switch (result.status) {
case 401:
return Promise.resolve({
api: result.api,
customRoutes: [
{
props: {
path: '/',
render: () => <Redirect to={`/login`} />,
},
},
],
});

default:
return Promise.reject(result);
}
},
);

export default props => (
<HydraAdmin
apiDocumentationParser={apiDocumentationParser}
authProvider={authProvider}
entrypoint={entrypoint}
dataProvider={dataProvider}
/>
);
6 changes: 4 additions & 2 deletions admin/src/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import { HydraAdmin } from '@api-platform/admin';
import Admin from './Admin';

export default () => <HydraAdmin entrypoint={process.env.REACT_APP_API_ENTRYPOINT}/>;
export default () => {
return <Admin entrypoint={process.env.REACT_APP_API_ENTRYPOINT} />;
};
49 changes: 49 additions & 0 deletions admin/src/authProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin';

// Change this to be your own login check route.
const login_uri = `${process.env.REACT_APP_API_ENTRYPOINT}/login_check`;

export default (type, params) => {
switch (type) {
case AUTH_LOGIN:
const { username, password } = params;
const request = new Request(`${login_uri}`, {
method: 'POST',
body: JSON.stringify({ login: username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});

return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}

return response.json();
})
.then(({ token }) => {
localStorage.setItem('token', token); // The JWT token is stored in the browser's local storage
window.location.replace('/');
});

case AUTH_LOGOUT:
localStorage.removeItem('token');
break;

case AUTH_ERROR:
if (401 === params.status) {
localStorage.removeItem('token');

return Promise.reject();
}
break;

case AUTH_CHECK:
return localStorage.getItem('token')
? Promise.resolve()
: Promise.reject();

default:
return Promise.resolve();
}
};
13 changes: 13 additions & 0 deletions api/.env
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,16 @@ CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
###< nelmio/cors-bundle ###

VARNISH_URL=http://cache-proxy

###> lexik/jwt-authentication-bundle ###
# JWT_PRIVATE_KEY=$(cat %kernel.project_dir%/config/jwt/private.pem | base64 | tr -d '\n')
# JWT_PUBLIC_KEY=$(cat %kernel.project_dir%/config/jwt/public.pem | base64 | tr -d '\n')
# JWT_PASSPHRASE=!ChangeMe!
###< lexik/jwt-authentication-bundle ###

###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:password@localhost"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###
4 changes: 4 additions & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
.php_cs
.php_cs.cache
###< friendsofphp/php-cs-fixer ###

###> lexik/jwt-authentication-bundle ###
/config/jwt/*.pem
###< lexik/jwt-authentication-bundle ###
11 changes: 11 additions & 0 deletions api/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/api-pack": "^1.1",
"doctrine/annotations": "^1.6",
"doctrine/doctrine-migrations-bundle": "^2.0",
"friendsofsymfony/user-bundle": "^2.1",
"guzzlehttp/guzzle": "^6.3",
"lexik/jwt-authentication-bundle": "^2.6",
"symfony/apache-pack": "^1.0",
"symfony/console": "4.2.*",
"symfony/dotenv": "4.2.*",
"symfony/flex": "^1.1",
"symfony/framework-bundle": "4.2.*",
"symfony/swiftmailer-bundle": "^3.2",
"symfony/translation": "4.2.*",
"symfony/validator": "4.2.*",
"symfony/yaml": "4.2.*"
},
"require-dev": {
"api-platform/schema-generator": "^2.1",
"behat/behat": "^3.5",
"doctrine/doctrine-fixtures-bundle": "^3.1",
"symfony/maker-bundle": "^1.11",
"symfony/profiler-pack": "^1.0"
},
"config": {
Expand Down
Loading