├── .gitignore ├── backend ├── web │ ├── modules │ │ └── .gitkeep │ ├── themes │ │ └── .gitkeep │ ├── profiles │ │ └── .gitkeep │ ├── .eslintrc.json │ ├── .eslintignore │ ├── .editorconfig │ ├── autoload.php │ ├── index.php │ ├── sites │ │ ├── development.services.yml │ │ ├── example.sites.php │ │ ├── example.settings.local.php │ │ └── default │ │ │ ├── default.services.yml │ │ │ └── default.settings.php │ ├── update.php │ ├── .csslintrc │ ├── robots.txt │ ├── .ht.router.php │ ├── .gitattributes │ ├── web.config │ └── .htaccess ├── drush │ ├── drush.yml │ ├── README.md │ ├── sites │ │ └── self.site.yml │ └── Commands │ │ └── PolicyCommands.php ├── config │ └── sync │ │ ├── README.txt │ │ └── .htaccess ├── load.environment.php ├── phpunit.xml.dist ├── .gitignore ├── .env.example ├── .travis.yml ├── composer.json ├── scripts │ └── composer │ │ └── ScriptHandler.php ├── README.md └── LICENSE ├── frontend ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── App.test.js │ ├── index.js │ ├── components │ │ ├── Button │ │ │ └── Button.js │ │ ├── Header │ │ │ ├── Header.js │ │ │ └── logo.svg │ │ ├── StatusMessage │ │ │ └── StatusMessage.js │ │ ├── SingleBlogPost │ │ │ └── SingleBlogPost.js │ │ └── BlogListing │ │ │ ├── BlogPost │ │ │ └── BlogPost.js │ │ │ └── BlogListing.js │ ├── App.js │ └── registerServiceWorker.js ├── .gitignore └── package.json ├── .lando.yml ├── README.md └── config ├── nginx └── site.conf └── drupal └── lando.settings.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store* -------------------------------------------------------------------------------- /backend/web/modules/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/web/themes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/web/profiles/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./core/.eslintrc.json" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgading/lando-decoupled/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /backend/drush/drush.yml: -------------------------------------------------------------------------------- 1 | # 2 | # A Drush configuration file 3 | # 4 | # Docs at https://github.com/drush-ops/drush/blob/master/examples/example.drush.yml 5 | # 6 | # Edit or remove this file as needed. -------------------------------------------------------------------------------- /backend/web/.eslintignore: -------------------------------------------------------------------------------- 1 | core/**/* 2 | vendor/**/* 3 | sites/**/files/**/* 4 | libraries/**/* 5 | sites/**/libraries/**/* 6 | profiles/**/libraries/**/* 7 | **/js_test_files/**/* 8 | **/node_modules/**/* 9 | -------------------------------------------------------------------------------- /backend/drush/README.md: -------------------------------------------------------------------------------- 1 | This directory contains commands, configuration and site aliases for Drush. See https://packagist.org/search/?type=drupal-drush for a directory of Drush commands installable via Composer. 2 | -------------------------------------------------------------------------------- /backend/config/sync/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains configuration to be imported into your Drupal site. To make this configuration active, visit admin/config/development/configuration/sync. For information about deploying configuration between servers, see https://www.drupal.org/documentation/administer/config -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /backend/drush/sites/self.site.yml: -------------------------------------------------------------------------------- 1 | # Edit or remove this file as needed. 2 | # Docs at https://github.com/drush-ops/drush/blob/master/examples/example.site.yml 3 | 4 | #prod: 5 | # host: prod.domain.com 6 | # user: www-admin 7 | # root: /path/to/drupal 8 | # uri: http://www.example.com 9 | # 10 | #stage: 11 | # host: stage.domain.com 12 | # user: www-admin 13 | # root: /path/to/drupal 14 | # uri: http://stage.example.com -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { injectGlobal } from 'styled-components'; 4 | 5 | import App from './App'; 6 | import registerServiceWorker from './registerServiceWorker'; 7 | 8 | ReactDOM.render(, document.getElementById('root')); 9 | registerServiceWorker(); 10 | 11 | injectGlobal` 12 | body { 13 | padding: 0; 14 | margin: 0; 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /backend/web/.editorconfig: -------------------------------------------------------------------------------- 1 | # Drupal editor configuration normalization 2 | # @see http://editorconfig.org/ 3 | 4 | # This is the top-most .editorconfig file; do not search in parent directories. 5 | root = true 6 | 7 | # All files. 8 | [*] 9 | end_of_line = LF 10 | indent_style = space 11 | indent_size = 2 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [composer.{json,lock}] 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /backend/web/autoload.php: -------------------------------------------------------------------------------- 1 | load(); 17 | } 18 | catch (InvalidPathException $e) { 19 | // Do nothing. Production environments rarely use .env files. 20 | } 21 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "prop-types": "^15.6.1", 8 | "react": "^16.4.0", 9 | "react-dom": "^16.4.0", 10 | "react-scripts": "1.1.4", 11 | "styled-components": "^3.3.2" 12 | }, 13 | "scripts": { 14 | "start": "HOST=0.0.0.0 PORT=3333 react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | ./test/ 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/components/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | const StyledButton = styled.button` 6 | background: none; 7 | border-radius: 0; 8 | border: #888 1px solid; 9 | `; 10 | 11 | 12 | 13 | const Button = ({text, click}) => { 14 | return( 15 | {text} 16 | ); 17 | } 18 | 19 | Button.propTypes = { 20 | text: PropTypes.string.isRequired, 21 | click: PropTypes.func.isRequired 22 | }; 23 | 24 | export default Button; 25 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore directories generated by Composer 2 | /drush/contrib/ 3 | /vendor/ 4 | /web/core/ 5 | /web/modules/contrib/ 6 | /web/themes/contrib/ 7 | /web/profiles/contrib/ 8 | /web/libraries/ 9 | 10 | # Ignore sensitive information 11 | /web/sites/*/settings.php 12 | /web/sites/*/settings.local.php 13 | 14 | # Ignore Drupal's file directory 15 | /web/sites/*/files/ 16 | 17 | # Ignore SimpleTest multi-site environment. 18 | /web/sites/simpletest 19 | 20 | # Ignore files generated by PhpStorm 21 | /.idea/ 22 | 23 | # Ignore .env files as they are personal 24 | /.env 25 | -------------------------------------------------------------------------------- /backend/web/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 20 | $response->send(); 21 | 22 | $kernel->terminate($request, $response); 23 | -------------------------------------------------------------------------------- /backend/config/sync/.htaccess: -------------------------------------------------------------------------------- 1 | # Deny all requests from Apache 2.4+. 2 | 3 | Require all denied 4 | 5 | 6 | # Deny all requests from Apache 2.0-2.2. 7 | 8 | Deny from all 9 | 10 | # Turn off all options we don't need. 11 | Options -Indexes -ExecCGI -Includes -MultiViews 12 | 13 | # Set the catch-all handler to prevent scripts from being executed. 14 | SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 15 | 16 | # Override the handler again if we're run later in the evaluation list. 17 | SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 18 | 19 | 20 | # If we know how to do it safely, disable the PHP engine entirely. 21 | 22 | php_flag engine off 23 | -------------------------------------------------------------------------------- /frontend/src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | import logo from './logo.svg'; 4 | 5 | 6 | const StyledHeader = styled.header` 7 | background-color: #222; 8 | height: 150px; 9 | padding: 20px; 10 | color: white; 11 | `; 12 | 13 | const SpinAnimation = keyframes` 14 | from { transform: rotate(0deg); } 15 | to { transform: rotate(360deg); } 16 | `; 17 | 18 | const Logo = styled.img` 19 | animation: ${SpinAnimation} infinite 20s linear; 20 | height: 80px; 21 | `; 22 | 23 | const Title = styled.h1` 24 | font-size: 1.5em; 25 | `; 26 | 27 | const Header = () => { 28 | return( 29 | 30 | 31 | Welcome to Decoupled Drupal 32 | 33 | ); 34 | } 35 | 36 | export default Header; 37 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # 2 | # Copy and rename this file to .env at root of this project. 3 | # 4 | 5 | # A common use case is to supply database creds via the environment. Edit settings.php 6 | # like so: 7 | # 8 | # $databases['default']['default'] = [ 9 | # 'database' => getenv('MYSQL_DATABASE'), 10 | # 'driver' => 'mysql', 11 | # 'host' => getenv('MYSQL_HOSTNAME'), 12 | # 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', 13 | # 'password' => getenv('MYSQL_PASSWORD'), 14 | # 'port' => getenv('MYSQL_PORT'), 15 | # 'prefix' => '', 16 | # 'username' => getenv('MYSQL_USER'), 17 | # ]; 18 | # 19 | # Uncomment and populate as needed. 20 | # MYSQL_DATABASE= 21 | # MYSQL_HOSTNAME= 22 | # MYSQL_PASSWORD= 23 | # MYSQL_PORT= 24 | # MYSQL_USER= 25 | 26 | # Another common use case is to set Drush's --uri via environment. 27 | # DRUSH_OPTIONS_URI=http://example.com 28 | -------------------------------------------------------------------------------- /backend/web/sites/development.services.yml: -------------------------------------------------------------------------------- 1 | # Local development services. 2 | # 3 | # To activate this feature, follow the instructions at the top of the 4 | # 'example.settings.local.php' file, which sits next to this file. 5 | parameters: 6 | http.response.debug_cacheability_headers: true 7 | cors.config: 8 | enabled: true 9 | # Specify allowed headers, like 'x-allowed-header'. 10 | allowedHeaders: [] 11 | # Specify allowed request methods, specify ['*'] to allow all possible ones. 12 | allowedMethods: [] 13 | # Configure requests allowed from specific origins. 14 | allowedOrigins: ['*'] 15 | # Sets the Access-Control-Expose-Headers header. 16 | exposedHeaders: false 17 | # Sets the Access-Control-Max-Age header. 18 | maxAge: false 19 | # Sets the Access-Control-Allow-Credentials header. 20 | supportsCredentials: false 21 | services: 22 | cache.backend.null: 23 | class: Drupal\Core\Cache\NullBackendFactory 24 | -------------------------------------------------------------------------------- /backend/web/update.php: -------------------------------------------------------------------------------- 1 | handle($request); 29 | $response->send(); 30 | 31 | $kernel->terminate($request, $response); 32 | -------------------------------------------------------------------------------- /frontend/src/components/StatusMessage/StatusMessage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const StyledStatusMessage = styled.div` 6 | width: 100%; 7 | padding: 10px; 8 | text-align: center; 9 | border: 2px solid #000; 10 | `; 11 | 12 | const StatusMessageError = StyledStatusMessage.extend` 13 | background: red; 14 | color: #fff; 15 | border: 2px solid red; 16 | `; 17 | 18 | const StatusMessage = ({message, type}) => { 19 | let StatusMessageContainer; 20 | switch (type) { 21 | case 'Error': 22 | StatusMessageContainer = StatusMessageError; 23 | break; 24 | default: 25 | StatusMessageContainer = StyledStatusMessage; 26 | break; 27 | } 28 | 29 | return( 30 | 31 |

{type}

32 |

{message}

33 |
34 | ); 35 | }; 36 | 37 | StatusMessage.propTypes = { 38 | message: PropTypes.string.isRequired, 39 | type: PropTypes.oneOf(['Error', 'Status', 'Notice']).isRequired, 40 | }; 41 | 42 | export default StatusMessage; 43 | -------------------------------------------------------------------------------- /backend/web/.csslintrc: -------------------------------------------------------------------------------- 1 | --errors=box-model, 2 | display-property-grouping, 3 | duplicate-background-images, 4 | duplicate-properties, 5 | empty-rules, 6 | ids, 7 | import, 8 | important, 9 | known-properties, 10 | outline-none, 11 | overqualified-elements, 12 | qualified-headings, 13 | shorthand, 14 | star-property-hack, 15 | text-indent, 16 | underscore-property-hack, 17 | unique-headings, 18 | unqualified-attributes, 19 | vendor-prefix, 20 | zero-units 21 | --ignore=adjoining-classes, 22 | box-sizing, 23 | bulletproof-font-face, 24 | compatible-vendor-prefixes, 25 | errors, 26 | fallback-colors, 27 | floats, 28 | font-faces, 29 | font-sizes, 30 | gradients, 31 | import-ie-limit, 32 | order-alphabetical, 33 | regex-selectors, 34 | rules-count, 35 | selector-max, 36 | selector-max-approaching, 37 | selector-newline, 38 | universal-selector 39 | --exclude-list=core/assets, 40 | vendor 41 | -------------------------------------------------------------------------------- /backend/drush/Commands/PolicyCommands.php: -------------------------------------------------------------------------------- 1 | input()->getArgument('target') == '@prod') { 23 | throw new \Exception(dt('Per !file, you may never overwrite the production database.', ['!file' => __FILE__])); 24 | } 25 | } 26 | 27 | /** 28 | * Limit rsync operations to production site. 29 | * 30 | * @hook validate core:rsync 31 | * 32 | * @throws \Exception 33 | */ 34 | public function rsyncValidate(CommandData $commandData) { 35 | if (preg_match("/^@prod/", $commandData->input()->getArgument('target'))) { 36 | throw new \Exception(dt('Per !file, you may never rsync to the production site.', ['!file' => __FILE__])); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/components/SingleBlogPost/SingleBlogPost.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | import Button from '../Button/Button'; 6 | 7 | const Image = styled.img` 8 | max-width: 300px; 9 | `; 10 | 11 | const SingleBlogPost = ({postDetails, returnToList}) => { 12 | function createMarkup() { 13 | return {__html: postDetails.postData.attributes.body.processed}; 14 | } 15 | return( 16 |
17 |