├── frontend
├── src
│ ├── shared
│ │ ├── action-types.js
│ │ └── api-client.js
│ ├── constants
│ │ ├── config.js
│ │ └── action-types.js
│ ├── index.js
│ ├── selectors
│ │ └── app.js
│ ├── components
│ │ └── sample.js
│ ├── containers
│ │ ├── app.css
│ │ └── app.js
│ ├── routes
│ │ └── index.js
│ ├── reducers
│ │ └── app.js
│ ├── actions
│ │ └── app.js
│ ├── store
│ │ └── index.js
│ └── registerServiceWorker.js
├── .dockerignore
├── public
│ ├── favicon.ico
│ ├── manifest.json
│ └── index.html
├── Dockerfile
├── .babelrc
├── config
│ ├── jest
│ │ ├── fileTransform.js
│ │ └── cssTransform.js
│ ├── polyfills.js
│ ├── paths.js
│ ├── env.js
│ ├── webpackDevServer.config.js
│ ├── webpack.config.dev.js
│ └── webpack.config.prod.js
├── .gitignore
├── scripts
│ ├── test.js
│ ├── start.js
│ └── build.js
└── package.json
├── backend
├── .rspec
├── app
│ ├── views
│ │ └── layouts
│ │ │ ├── mailer.text.erb
│ │ │ └── mailer.html.erb
│ ├── jobs
│ │ ├── application_job.rb
│ │ └── test_job.rb
│ ├── models
│ │ ├── application_record.rb
│ │ └── user.rb
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ └── controllers
│ │ ├── application_controller.rb
│ │ └── api
│ │ └── status_controller.rb
├── public
│ └── robots.txt
├── bin
│ ├── bundle
│ ├── rake
│ ├── rails
│ ├── spring
│ ├── update
│ └── setup
├── config
│ ├── boot.rb
│ ├── spring.rb
│ ├── environment.rb
│ ├── initializers
│ │ ├── mime_types.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── application_controller_renderer.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── wrap_parameters.rb
│ │ ├── cors.rb
│ │ ├── inflections.rb
│ │ └── devise_token_auth.rb
│ ├── cable.yml
│ ├── routes.rb
│ ├── locales
│ │ └── en.yml
│ ├── application.rb
│ ├── secrets.yml
│ ├── environments
│ │ ├── development.rb
│ │ ├── test.rb
│ │ └── production.rb
│ ├── puma.rb
│ └── database.yml
├── .dockerignore
├── config.ru
├── docker-entrypoint.sh
├── Rakefile
├── db
│ ├── seeds.rb
│ ├── migrate
│ │ └── 20180306161431_devise_token_auth_create_users.rb
│ └── schema.rb
├── .gitignore
├── Dockerfile
├── Gemfile
├── spec
│ └── spec_helper.rb
└── Gemfile.lock
├── .gitignore
├── CONTRIBUTING.md
├── config
└── nginx
│ └── nginx.conf
├── docker-compose.yml
└── README.md
/frontend/src/shared/action-types.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/.rspec:
--------------------------------------------------------------------------------
1 | --require spec_helper
2 |
--------------------------------------------------------------------------------
/backend/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/frontend/src/constants/config.js:
--------------------------------------------------------------------------------
1 | export const baseApiUri = '/api';
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | terraform.tfvars
2 | .terraform
3 | *.tfstate*
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/frontend/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .idea
3 | node_modules
4 | build
5 | npm-debug.log*
6 | npm-error.log*
--------------------------------------------------------------------------------
/backend/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | queue_as :default
3 | end
4 |
--------------------------------------------------------------------------------
/backend/app/jobs/test_job.rb:
--------------------------------------------------------------------------------
1 | class TestJob < ApplicationJob
2 | def perform
3 | # Do some work
4 | end
5 | end
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adaam2/docker-rails-react-starter/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/backend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/backend/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/backend/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/backend/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/backend/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/backend/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/backend/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/backend/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/backend/.dockerignore:
--------------------------------------------------------------------------------
1 | tmp/*
2 | log/*
3 | db/*.sqlite3
4 | .git
5 | .byebug_history
6 |
7 | # Dont copy the docker ignore to the VD. Makes no sense
8 | .dockerignore
--------------------------------------------------------------------------------
/backend/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/backend/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | if [ -f tmp/pids/server.pid ]; then
5 | rm tmp/pids/server.pid
6 | fi
7 |
8 | exec bundle exec "$@"
9 |
--------------------------------------------------------------------------------
/backend/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::API
2 | include DeviseTokenAuth::Concerns::SetUserByToken
3 | end
4 |
--------------------------------------------------------------------------------
/backend/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:9.4.0
2 | WORKDIR /usr/src/frontend-app
3 | COPY package*.json ./
4 | RUN yarn install
5 | COPY . .
6 | EXPOSE 3000
7 |
8 | CMD ["yarn", "start"]
--------------------------------------------------------------------------------
/backend/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/frontend/src/constants/action-types.js:
--------------------------------------------------------------------------------
1 | export const ACTION_TYPES = {
2 | APP_LOAD: 'APP_LOAD',
3 | API_STATUS_CHECK_SUCCESS: 'API_STATUS_CHECK_SUCCESS',
4 | API_STATUS_CHECK_FAILURE: 'API_STATUS_CHECK_FAILURE'
5 | };
--------------------------------------------------------------------------------
/backend/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 | channel_prefix: backend_production
11 |
--------------------------------------------------------------------------------
/backend/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/backend/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/backend/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/backend/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/backend/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ApplicationRecord
2 | # Include default devise modules.
3 | devise :database_authenticatable, :registerable,
4 | :recoverable, :rememberable, :trackable, :validatable,
5 | :confirmable, :omniauthable
6 | include DeviseTokenAuth::Concerns::User
7 | end
8 |
--------------------------------------------------------------------------------
/backend/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/backend/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/frontend/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react",
4 | "babel-preset-env",
5 | "stage-0"
6 | ],
7 | "plugins": [
8 | "transform-runtime",
9 | "add-module-exports",
10 | "transform-decorators-legacy"
11 | ],
12 | "env": {
13 | "development": {
14 | "plugins": []
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/backend/config/routes.rb:
--------------------------------------------------------------------------------
1 | require 'sidekiq/web'
2 |
3 | Rails.application.routes.draw do
4 | root to: 'api/status#index'
5 |
6 | namespace :api do
7 | mount_devise_token_auth_for 'User', at: 'auth'
8 |
9 | namespace :jobs do
10 | mount Sidekiq::Web => '/ui'
11 | end
12 |
13 | get '/', to: 'status#index'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/frontend/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/en/webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/backend/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/frontend/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Provider } from 'react-redux';
4 |
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | import store from './store';
8 | import routes from './routes';
9 |
10 | render(
11 | {routes},
12 | document.getElementById('root')
13 | );
14 | registerServiceWorker();
15 |
--------------------------------------------------------------------------------
/backend/app/controllers/api/status_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::StatusController < ApplicationController
2 | def index
3 | render json: { status: "OK" }
4 | end
5 |
6 | def job
7 | # Just an example of enqueuing an ActiveJob to the worker container.
8 | # perform_later will cause it to be enqueud to the worker
9 | # whilst perform will cause it to be executed in the current Rails process
10 | TestJob.perform_later
11 | end
12 | end
--------------------------------------------------------------------------------
/backend/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/frontend/src/selectors/app.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import _ from 'lodash';
3 |
4 | export const getAppStore = store => store.app;
5 |
6 | export const getApiStatus = createSelector(
7 | [getAppStore],
8 | (appStore) => {
9 | return _.get(appStore, 'apiStatus');
10 | }
11 | );
12 |
13 | export const getLoadedStatus = createSelector(
14 | [getAppStore],
15 | (appStore) => {
16 | return _.get(appStore, 'loaded');
17 | }
18 | );
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the boilerplate
2 |
3 | Please clone this repo, and follow the setup steps to get the project up and running.
4 |
5 | If you would like to add a feature, then generally I'll be all for it. But ensure that your feature is as general purpose and universal as possible. This project is intended to be a starter kit, so I want to keep it simple.
6 |
7 | Once you think you have completed your work, please submit a pull request from your branch to this repo.
8 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 |
14 | .byebug_history
15 |
--------------------------------------------------------------------------------
/frontend/src/components/sample.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Sample extends React.Component {
4 | render() {
5 | return (
6 |
7 |
8 | Information
9 |
10 |
11 | The Rails application can be found namespaced at http://localhost:8080/api.
The root route is the API status page.
12 |
13 |
14 | )
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/containers/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | text-align: center;
5 | }
6 |
7 | header, main {
8 | padding: 1rem;
9 | }
10 |
11 | header {
12 | background: #333;
13 | min-height: 100px;
14 | color: #fff;
15 | }
16 |
17 | p {
18 | line-height: 1.5;
19 | }
20 |
21 | .StatusPill {
22 | border-radius: 8px;
23 | padding: 0.25rem 0.5rem;
24 | width: 80px;
25 | text-align: center;
26 | margin-left: 15px;
27 | color: white;
28 | font-weight: bold;
29 | background: green;
30 | margin: auto 5px;
31 | }
--------------------------------------------------------------------------------
/backend/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/backend/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/frontend/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Switch, Route } from 'react-router';
3 | import { ConnectedRouter } from 'connected-react-router';
4 | import { history } from '../store';
5 |
6 | // Layouts
7 | import App from '../containers/app';
8 |
9 | // Components
10 | import Test from '../components/sample';
11 |
12 | const routes = (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
22 | export default routes;
--------------------------------------------------------------------------------
/backend/config/initializers/cors.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Avoid CORS issues when API is called from the frontend app.
4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
5 |
6 | # Read more: https://github.com/cyu/rack-cors
7 |
8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do
9 | # allow do
10 | # origins 'example.com'
11 | #
12 | # resource '*',
13 | # headers: :any,
14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head]
15 | # end
16 | # end
17 |
--------------------------------------------------------------------------------
/frontend/src/reducers/app.js:
--------------------------------------------------------------------------------
1 | import { ACTION_TYPES } from '../constants/action-types';
2 |
3 | const initialState = {
4 | loaded: false,
5 | apiStatus: null
6 | };
7 |
8 | export default function app(state = initialState, action) {
9 | switch (action.type) {
10 | case ACTION_TYPES.APP_LOAD:
11 | return { ...state, loaded: true };
12 | case ACTION_TYPES.API_STATUS_CHECK_SUCCESS:
13 | return { ...state, apiStatus: action.status }
14 | case ACTION_TYPES.API_STATUS_CHECK_FAILURE:
15 | return { ...state, apiStatus: "PROBLEMO" }
16 | default:
17 | return state;
18 | }
19 | }
--------------------------------------------------------------------------------
/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:2.3.3
2 | RUN apt-get update -qq && apt-get install -y --no-install-recommends build-essential libpq-dev
3 | RUN apt-get clean
4 | RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
5 | RUN gem install bundler
6 |
7 | RUN mkdir /usr/src/backend-app
8 | WORKDIR /usr/src/backend-app
9 |
10 | RUN echo "gem: --no-rdoc --no-ri" > /etc/gemrc
11 | ADD Gemfile /usr/src/backend-app/Gemfile
12 | ADD Gemfile.lock /usr/src/backend-app/Gemfile.lock
13 | RUN bundle install --jobs 20 --retry 5
14 | ADD . /usr/src/backend-app
15 | EXPOSE 3000
16 |
17 | COPY ./docker-entrypoint.sh /
18 | RUN chmod +x /docker-entrypoint.sh
19 |
20 | ENTRYPOINT ["/docker-entrypoint.sh"]
21 | CMD ["rails", "s", "-b", "0.0.0.0"]
22 |
--------------------------------------------------------------------------------
/backend/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/frontend/src/actions/app.js:
--------------------------------------------------------------------------------
1 | import { ACTION_TYPES } from '../constants/action-types';
2 | import ApiClient from '../shared/api-client';
3 |
4 | export function loadApp() {
5 | return {
6 | type: ACTION_TYPES.APP_LOAD,
7 | };
8 | }
9 |
10 | function ApiSuccess(status) {
11 | return {
12 | type: ACTION_TYPES.API_STATUS_CHECK_SUCCESS,
13 | status
14 | }
15 | }
16 |
17 | function ApiFailure(err) {
18 | return {
19 | type: ACTION_TYPES.API_STATUS_CHECK_FAILURE,
20 | err
21 | }
22 | }
23 |
24 | const STATUS_URI = "/";
25 |
26 | export function checkApiStatus() {
27 | return function(dispatch) {
28 | return ApiClient.get(STATUS_URI)
29 | .then((response) => {
30 | dispatch(ApiSuccess(response.body.status));
31 | },
32 | (error) => {
33 | dispatch(ApiFailure(error));
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------
/frontend/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/backend/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/backend/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) do |repo_name|
4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 | "https://github.com/#{repo_name}.git"
6 | end
7 |
8 | gem 'rails', '~> 5.1.5'
9 | gem 'pg', '>= 0.18', '< 2.0'
10 | gem 'puma', '~> 3.7'
11 |
12 | gem 'devise_token_auth'
13 | gem 'omniauth'
14 | gem 'redis'
15 | gem 'sidekiq'
16 | gem 'connection_pool'
17 |
18 | group :development, :test do
19 | gem 'pry'
20 | gem 'pry-rails'
21 | gem 'rspec'
22 | end
23 |
24 | group :development do
25 | gem 'listen', '>= 3.0.5', '< 3.2'
26 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
27 | gem 'spring'
28 | gem 'spring-watcher-listen', '~> 2.0.0'
29 | end
30 |
31 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
32 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
33 |
--------------------------------------------------------------------------------
/frontend/src/shared/api-client.js:
--------------------------------------------------------------------------------
1 | import request from 'superagent';
2 | import { baseApiUri } from '../constants/config';
3 |
4 | class ApiClient {
5 | constructor(baseUri) {
6 | this.baseUri = baseUri;
7 | }
8 |
9 | get(path, headers = {}, params = {}) {
10 | return this.sendRequest('GET', path, params);
11 | }
12 |
13 | post(path, headers = {}, body = {}) {
14 | return this.sendRequest('POST', path, body);
15 | }
16 |
17 | delete(path, headers = {}, params = {}) {
18 | return this.sendRequest('DELETE', path, params);
19 | }
20 |
21 | patch(path, headers = {}, body = {}) {
22 | return this.sendRequest('PATCH', path, body);
23 | }
24 |
25 | requestUrl(path) {
26 | return `${this.baseUri}/${path}`;
27 | }
28 |
29 | sendRequest(method, path, headers = {}, params = {}) {
30 | return request(method, this.requestUrl(path), params).set(headers);
31 | }
32 | }
33 |
34 | export default new ApiClient(baseApiUri);
--------------------------------------------------------------------------------
/frontend/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/config/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | # Similar approach we have in production, except that we do
2 | # not serve static files for the client nor assets, we upstream
3 | # everything
4 |
5 | server {
6 | listen 8080;
7 | server_name yourproject.docker;
8 |
9 | location /api {
10 | proxy_set_header Host $host;
11 | proxy_set_header X-Real-IP $remote_addr;
12 | proxy_pass http://backend:3000;
13 | }
14 |
15 | # Other to frontend
16 | location /sockjs-node {
17 | proxy_set_header X-Real-IP $remote_addr;
18 | proxy_set_header X-Forwarded-For $remote_addr;
19 | proxy_set_header Host $host;
20 |
21 | proxy_pass http://frontend:4000;
22 |
23 | proxy_redirect off;
24 |
25 | proxy_http_version 1.1;
26 | proxy_set_header Upgrade $http_upgrade;
27 | proxy_set_header Connection "upgrade";
28 | }
29 |
30 | location / {
31 | proxy_set_header Host $host;
32 | proxy_set_header X-Real-IP $remote_addr;
33 | proxy_pass http://frontend:4000;
34 | }
35 | }
--------------------------------------------------------------------------------
/backend/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/backend/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 |
22 | # puts "\n== Copying sample files =="
23 | # unless File.exist?('config/database.yml')
24 | # cp 'config/database.yml.sample', 'config/database.yml'
25 | # end
26 |
27 | puts "\n== Preparing database =="
28 | system! 'bin/rails db:setup'
29 |
30 | puts "\n== Removing old logs and tempfiles =="
31 | system! 'bin/rails log:clear tmp:clear'
32 |
33 | puts "\n== Restarting application server =="
34 | system! 'bin/rails restart'
35 | end
36 |
--------------------------------------------------------------------------------
/frontend/src/store/index.js:
--------------------------------------------------------------------------------
1 | // Create final store using all reducers and applying middleware
2 | import { createBrowserHistory } from 'history';
3 | // Redux utility functions
4 | import { compose, createStore, combineReducers, applyMiddleware } from 'redux';
5 | import { routerMiddleware, connectRouter } from 'connected-react-router';
6 | import thunk from 'redux-thunk';
7 |
8 | // Import all reducers
9 | import app from '../reducers/app';
10 |
11 | import logger from 'redux-logger';
12 |
13 | // Configure reducer to store state at state.router
14 | // You can store it elsewhere by specifying a custom `routerStateSelector`
15 | // in the store enhancer below
16 | export const history = createBrowserHistory();
17 | const reducer = combineReducers({ app });
18 |
19 | const middlewares = [
20 | routerMiddleware(history),
21 | thunk,
22 | logger
23 | ];
24 |
25 | const store = compose(
26 | // Enables your middleware:
27 | // applyMiddleware(thunk), // any Redux middleware, e.g. redux-thunk
28 | applyMiddleware(...middlewares),
29 | // Provides support for DevTools via Chrome extension
30 | window.devToolsExtension ? window.devToolsExtension() : f => f
31 | )(createStore)(connectRouter(history)(reducer));
32 |
33 | export default store;
--------------------------------------------------------------------------------
/backend/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 |
3 | require "rails"
4 | # Pick the frameworks you want:
5 | require "active_model/railtie"
6 | require "active_job/railtie"
7 | require "active_record/railtie"
8 | require "action_controller/railtie"
9 | require "action_mailer/railtie"
10 | require "action_view/railtie"
11 | require "action_cable/engine"
12 | # require "sprockets/railtie"
13 |
14 | # Require the gems listed in Gemfile, including any gems
15 | # you've limited to :test, :development, or :production.
16 | Bundler.require(*Rails.groups)
17 |
18 | module Backend
19 | class Application < Rails::Application
20 | # Initialize configuration defaults for originally generated Rails version.
21 | config.load_defaults 5.1
22 |
23 | config.active_job.queue_adapter = :sidekiq
24 |
25 | # Settings in config/environments/* take precedence over those specified here.
26 | # Application configuration should go into files in config/initializers
27 | # -- all .rb files in that directory are automatically loaded.
28 |
29 | # Only loads a smaller set of middleware suitable for API only apps.
30 | # Middleware like session, flash, cookies can be added back manually.
31 | # Skip views, helpers and assets when generating a new resource.
32 | config.api_only = true
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/backend/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: b1c317da8ce5a4da280e2c80f39f0439ce804840332a234d47bd50edd3cc5ba9dec6a3023423d73168c2b9f5886c585e66ddda6aba73051ac1921c758533a847
22 |
23 | test:
24 | secret_key_base: e368e9a07bea06b78aadbf976746caa825688ac667da2706ed6dcd4f7638f288702beb6ee93a704c1b08b9b8921c14e570e9e52dfb886ec23c9e124b8e6cedc6
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
33 |
--------------------------------------------------------------------------------
/frontend/src/containers/app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { loadApp, checkApiStatus } from '../actions/app';
4 | import * as AppSelectors from '../selectors/app';
5 |
6 | // eslint-disable-next-line
7 | import styles from './app.css';
8 |
9 | type Props = {
10 | dispatch: () => void,
11 | loaded: boolean,
12 | children: array
13 | }
14 |
15 | class App extends React.Component {
16 | props: Props;
17 |
18 | componentDidMount() {
19 | this.props.dispatch(loadApp());
20 | this.props.dispatch(checkApiStatus());
21 | }
22 |
23 | render() {
24 | if (!this.props.loaded) {
25 | return (
26 |
27 | Loading..
28 |
29 | );
30 | }
31 |
32 | return (
33 |
34 |
35 | React + Redux + Rails + Docker Compose
36 |
37 |
38 | With redux, thunk actions, redux action logging
39 |
40 |
41 | API Status:
42 |
43 | {this.props.apiStatus}
44 |
45 |
46 |
47 |
48 |
49 | {this.props.children}
50 |
51 |
52 | )
53 | }
54 | }
55 |
56 | export default connect((store, props) => {
57 | return {
58 | loaded: AppSelectors.getLoadedStatus(store),
59 | apiStatus: AppSelectors.getApiStatus(store)
60 | };
61 | })(App);
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | redis:
5 | image: redis
6 | volumes:
7 | - ./backend/tmp/redis:/data
8 | ports:
9 | - 6379:6379
10 | worker:
11 | build: backend
12 | command: bundle exec sidekiq
13 | environment:
14 | REDIS_URL: redis://redis:6379/0
15 | volumes:
16 | - ./backend:/usr/src/backend-app
17 | depends_on:
18 | - db
19 | - redis
20 | nginx:
21 | image: bitnami/nginx:1.10.2-r1
22 | volumes:
23 | - ./config/nginx:/bitnami/nginx/conf/vhosts
24 | depends_on:
25 | - backend
26 | - frontend
27 | environment:
28 | VIRTUAL_HOST: yourproject.docker
29 | VIRTUAL_PORT: 8080
30 | ports:
31 | - 8080:8080
32 | db:
33 | image: postgres
34 | volumes:
35 | - /var/lib/postgresql/data
36 | environment:
37 | POSTGRES_USER: postgres
38 | POSTGRES_PASSWORD: secret
39 | ALLOW_IP_RANGE: 0.0.0.0/0
40 | ports:
41 | - 54321:5432
42 | backend:
43 | build: backend
44 | depends_on:
45 | - db
46 | - redis
47 | - worker
48 | volumes:
49 | - ./backend:/usr/src/backend-app
50 | environment:
51 | REDIS_URL: redis://redis:6379/0
52 | SIDEKIQ_REDIS_URL: redis://redis:6379/1
53 | frontend:
54 | build:
55 | context: ./frontend/
56 | depends_on:
57 | - backend
58 | command: npm start
59 | volumes:
60 | - ./frontend/:/usr/src/frontend-app
61 | - ./frontend/node_modules:/usr/src/frontend-app/node_modules
62 | ports:
63 | - "35729:35729"
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/backend/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 |
43 | # Use an evented file watcher to asynchronously detect changes in source code,
44 | # routes, locales, etc. This feature depends on the listen gem.
45 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
46 | end
47 |
--------------------------------------------------------------------------------
/backend/db/migrate/20180306161431_devise_token_auth_create_users.rb:
--------------------------------------------------------------------------------
1 | class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table(:users) do |t|
4 | ## Required
5 | t.string :provider, :null => false, :default => "email"
6 | t.string :uid, :null => false, :default => ""
7 |
8 | ## Database authenticatable
9 | t.string :encrypted_password, :null => false, :default => ""
10 |
11 | ## Recoverable
12 | t.string :reset_password_token
13 | t.datetime :reset_password_sent_at
14 |
15 | ## Rememberable
16 | t.datetime :remember_created_at
17 |
18 | ## Trackable
19 | t.integer :sign_in_count, :default => 0, :null => false
20 | t.datetime :current_sign_in_at
21 | t.datetime :last_sign_in_at
22 | t.string :current_sign_in_ip
23 | t.string :last_sign_in_ip
24 |
25 | ## Confirmable
26 | t.string :confirmation_token
27 | t.datetime :confirmed_at
28 | t.datetime :confirmation_sent_at
29 | t.string :unconfirmed_email # Only if using reconfirmable
30 |
31 | ## Lockable
32 | # t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
33 | # t.string :unlock_token # Only if unlock strategy is :email or :both
34 | # t.datetime :locked_at
35 |
36 | ## User Info
37 | t.string :name
38 | t.string :nickname
39 | t.string :image
40 | t.string :email
41 |
42 | ## Tokens
43 | t.json :tokens
44 |
45 | t.timestamps
46 | end
47 |
48 | add_index :users, :email, unique: true
49 | add_index :users, [:uid, :provider], unique: true
50 | add_index :users, :reset_password_token, unique: true
51 | add_index :users, :confirmation_token, unique: true
52 | # add_index :users, :unlock_token, unique: true
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/backend/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/frontend/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right