Browse Source

Initial Commit

ANX 5 years ago
commit
e104460102
35 changed files with 898 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 21 0
      LICENSE
  3. 3 0
      Procfile
  4. 39 0
      README.md
  5. 22 0
      app/assets/stylesheets/application.scss
  6. 32 0
      app/assets/stylesheets/sticky-footer.scss
  7. 20 0
      app/channels/application_cable/connection.rb
  8. 13 0
      app/controllers/announcements_controller.rb
  9. 13 0
      app/controllers/application_controller.rb
  10. 10 0
      app/controllers/home_controller.rb
  11. 80 0
      app/controllers/users/omniauth_callbacks_controller.rb
  12. 10 0
      app/helpers/application_helper.rb
  13. 14 0
      app/helpers/devise_helper.rb
  14. 32 0
      app/models/service.rb
  15. 10 0
      app/models/user.rb
  16. 24 0
      app/views/admin/application/_navigation.html.erb
  17. 51 0
      app/views/admin/users/show.html.erb
  18. 42 0
      app/views/devise/registrations/edit.html.erb
  19. 33 0
      app/views/devise/registrations/new.html.erb
  20. 2 0
      app/views/home/index.html.erb
  21. 2 0
      app/views/home/privacy.html.erb
  22. 2 0
      app/views/home/terms.html.erb
  23. 17 0
      app/views/layouts/application.html.erb
  24. 9 0
      app/views/shared/_footer.html.erb
  25. 7 0
      app/views/shared/_head.html.erb
  26. 51 0
      app/views/shared/_navbar.html.erb
  27. 8 0
      app/views/shared/_notices.html.erb
  28. 13 0
      config/cable.yml
  29. 4 0
      config/initializers/gravatar.rb
  30. 46 0
      lib/templates/erb/scaffold/_form.html.erb
  31. 3 0
      lib/templates/erb/scaffold/edit.html.erb
  32. 51 0
      lib/templates/erb/scaffold/index.html.erb
  33. 3 0
      lib/templates/erb/scaffold/new.html.erb
  34. 19 0
      lib/templates/erb/scaffold/show.html.erb
  35. 188 0
      template.rb

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+.DS_Store
+.idea
+.idea/
+

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Ivan Chen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 3 - 0
Procfile

@@ -0,0 +1,3 @@
+web: rails server
+sidekiq: sidekiq
+webpack: bin/webpack-dev-server

+ 39 - 0
README.md

@@ -0,0 +1,39 @@
+# Rails Jumpstart
+
+It's like Laravel Spark, for Rails. All your Rails apps should start off with a bunch of great defaults.
+
+**Note:** Requires Rails 5.2
+
+## Getting Started
+
+Jumpstart is a Rails template, so you pass it in as an option when creating a new app.
+
+#### Requirements
+
+You'll need the following installed to run the template successfully:
+
+* Ruby 2.5+
+* bundler - `gem install bundler`
+* rails - `gem install rails`
+* Yarn - `brew install yarn` or [Install Yarn](https://yarnpkg.com/en/docs/install)
+
+#### Creating a new app
+
+```bash
+rails new myapp -d postgresql -m https://raw.githubusercontent.com/excid3/jumpstart/master/template.rb
+```
+
+Or if you have downloaded this repo, you can reference template.rb locally:
+
+```bash
+rails new myapp -d postgresql -m template.rb
+```
+
+#### Cleaning up
+
+```bash
+rails db:drop
+spring stop
+cd ..
+rm -rf myapp
+```

+ 22 - 0
app/assets/stylesheets/application.scss

@@ -0,0 +1,22 @@
+// $navbar-default-bg: #312312;
+// $light-orange: #ff8c00;
+// $navbar-default-color: $light-orange;
+
+@import "font-awesome-sprockets";
+@import "font-awesome";
+@import "bootstrap";
+@import "sticky-footer";
+@import "announcements";
+
+// Fixes bootstrap nav-brand container overlap
+@include media-breakpoint-down(xs) {
+  .container {
+    margin-left: 0;
+    margin-right: 0;
+  }
+}
+
+// Masquerade alert shouldn't have a bottom margin
+body > .alert {
+  margin-bottom: 0;
+}

+ 32 - 0
app/assets/stylesheets/sticky-footer.scss

@@ -0,0 +1,32 @@
+/* Sticky footer styles
+-------------------------------------------------- */
+html {
+  position: relative;
+  min-height: 100%;
+}
+body {
+  /* Margin bottom by footer height */
+  margin-bottom: 60px;
+}
+.footer {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  /* Set the fixed height of the footer here */
+  height: 60px;
+  line-height: 60px; /* Vertically center the text there */
+}
+
+
+/* Custom page CSS
+-------------------------------------------------- */
+/* Not required for template or sticky footer method. */
+
+body > .container {
+  padding: 40px 15px 0;
+}
+
+.footer > .container {
+  padding-right: 15px;
+  padding-left: 15px;
+}

+ 20 - 0
app/channels/application_cable/connection.rb

@@ -0,0 +1,20 @@
+module ApplicationCable
+  class Connection < ActionCable::Connection::Base
+    identified_by :current_user
+
+    def connect
+      self.current_user = find_verified_user
+      logger.add_tags "ActionCable", "User #{current_user.id}"
+    end
+
+    protected
+
+      def find_verified_user
+        if current_user = env['warden'].user
+          current_user
+        else
+          reject_unauthorized_connection
+        end
+      end
+  end
+end

+ 13 - 0
app/controllers/announcements_controller.rb

@@ -0,0 +1,13 @@
+class AnnouncementsController < ApplicationController
+  before_action :mark_as_read, if: :user_signed_in?
+
+  def index
+    @announcements = Announcement.order(published_at: :desc)
+  end
+
+  private
+
+    def mark_as_read
+      current_user.update(announcements_last_read_at: Time.zone.now)
+    end
+end

+ 13 - 0
app/controllers/application_controller.rb

@@ -0,0 +1,13 @@
+class ApplicationController < ActionController::Base
+  protect_from_forgery with: :exception
+
+  before_action :configure_permitted_parameters, if: :devise_controller?
+  before_action :masquerade_user!
+
+  protected
+
+    def configure_permitted_parameters
+      devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
+      devise_parameter_sanitizer.permit(:account_update, keys: [:name])
+    end
+end

+ 10 - 0
app/controllers/home_controller.rb

@@ -0,0 +1,10 @@
+class HomeController < ApplicationController
+  def index
+  end
+
+  def terms
+  end
+
+  def privacy
+  end
+end

+ 80 - 0
app/controllers/users/omniauth_callbacks_controller.rb

@@ -0,0 +1,80 @@
+module Users
+  class OmniauthCallbacksController < Devise::OmniauthCallbacksController
+    before_action :set_service
+    before_action :set_user
+
+    attr_reader :service, :user
+
+    def facebook
+      handle_auth "Facebook"
+    end
+
+    def twitter
+      handle_auth "Twitter"
+    end
+
+    def github
+      handle_auth "Github"
+    end
+
+    private
+
+    def handle_auth(kind)
+      if service.present?
+        service.update(service_attrs)
+      else
+        user.services.create(service_attrs)
+      end
+
+      if user_signed_in?
+        flash[:notice] = "Your #{kind} account was connected."
+        redirect_to edit_user_registration_path
+      else
+        sign_in_and_redirect user, event: :authentication
+        set_flash_message :notice, :success, kind: kind
+      end
+    end
+
+    def auth
+      request.env['omniauth.auth']
+    end
+
+    def set_service
+      @service = Service.where(provider: auth.provider, uid: auth.uid).first
+    end
+
+    def set_user
+      if user_signed_in?
+        @user = current_user
+      elsif service.present?
+        @user = service.user
+      elsif User.where(email: auth.info.email).any?
+        # 5. User is logged out and they login to a new account which doesn't match their old one
+        flash[:alert] = "An account with this email already exists. Please sign in with that account before connecting your #{auth.provider.titleize} account."
+        redirect_to new_user_session_path
+      else
+        @user = create_user
+      end
+    end
+
+    def service_attrs
+      expires_at = auth.credentials.expires_at.present? ? Time.at(auth.credentials.expires_at) : nil
+      {
+          provider: auth.provider,
+          uid: auth.uid,
+          expires_at: expires_at,
+          access_token: auth.credentials.token,
+          access_token_secret: auth.credentials.secret,
+      }
+    end
+
+    def create_user
+      User.create(
+        email: auth.info.email,
+        #name: auth.info.name,
+        password: Devise.friendly_token[0,20]
+      )
+    end
+
+  end
+end

+ 10 - 0
app/helpers/application_helper.rb

@@ -0,0 +1,10 @@
+module ApplicationHelper
+  def bootstrap_class_for(flash_type)
+    {
+      success: "alert-success",
+      error: "alert-danger",
+      alert: "alert-warning",
+      notice: "alert-info"
+    }.stringify_keys[flash_type.to_s] || flash_type.to_s
+  end
+end

+ 14 - 0
app/helpers/devise_helper.rb

@@ -0,0 +1,14 @@
+module DeviseHelper
+  def devise_error_messages!
+    return '' if resource.errors.empty?
+
+    messages = resource.errors.full_messages.map { |msg| content_tag(:div, msg) }.join
+    html = <<-HTML
+    <div class="alert alert-danger">
+      #{messages}
+    </div>
+    HTML
+
+    html.html_safe
+  end
+end

+ 32 - 0
app/models/service.rb

@@ -0,0 +1,32 @@
+class Service < ApplicationRecord
+  belongs_to :user
+
+  %w{ facebook twitter github }.each do |provider|
+    scope provider, ->{ where(provider: provider) }
+  end
+
+  def client
+    send("#{provider}_client")
+  end
+
+  def expired?
+    expires_at? && expires_at <= Time.zone.now
+  end
+
+  def access_token
+    send("#{provider}_refresh_token!", super) if expired?
+    super
+  end
+
+
+  def twitter_client
+    Twitter::REST::Client.new do |config|
+      config.consumer_key        = Rails.application.secrets.twitter_app_id
+      config.consumer_secret     = Rails.application.secrets.twitter_app_secret
+      config.access_token        = access_token
+      config.access_token_secret = access_token_secret
+    end
+  end
+
+  def twitter_refresh_token!(token); end
+end

+ 10 - 0
app/models/user.rb

@@ -0,0 +1,10 @@
+class User < ApplicationRecord
+  # Include default devise modules. Others available are:
+  # :confirmable, :lockable, :timeoutable and :omniauthable
+  devise :masqueradable, :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :omniauthable
+
+  has_person_name
+
+  has_many :notifications, foreign_key: :recipient_id
+  has_many :services
+end

+ 24 - 0
app/views/admin/application/_navigation.html.erb

@@ -0,0 +1,24 @@
+<%#
+# Navigation
+
+This partial is used to display the navigation in Administrate.
+By default, the navigation contains navigation links
+for all resources in the admin dashboard,
+as defined by the routes in the `admin/` namespace
+%>
+
+<nav class="navigation" role="navigation">
+  <%= link_to(
+    "← Back to App",
+    root_path,
+    class: "navigation__link"
+  ) %>
+
+  <% Administrate::Namespace.new(namespace).resources.each do |resource| %>
+    <%= link_to(
+      display_resource_name(resource),
+      [namespace, resource_index_route_key(resource)],
+      class: "navigation__link navigation__link--#{nav_link_state(resource)}"
+    ) %>
+  <% end %>
+</nav>

+ 51 - 0
app/views/admin/users/show.html.erb

@@ -0,0 +1,51 @@
+<%#
+# Show
+
+This view is the template for the show page.
+It renders the attributes of a resource,
+as well as a link to its edit page.
+
+## Local variables:
+
+- `page`:
+  An instance of [Administrate::Page::Show][1].
+  Contains methods for accessing the resource to be displayed on the page,
+  as well as helpers for describing how each attribute of the resource
+  should be displayed.
+
+[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show
+%>
+
+<% content_for(:title) { t("administrate.actions.show_resource", {name: page.page_title}) } %>
+
+<header class="main-content__header" role="banner">
+  <h1 class="main-content__page-title">
+    <%= content_for(:title) %>
+  </h1>
+
+  <div>
+    <%= link_to "Login As User", masquerade_path(page.resource), class: "button" %>
+
+    <%= link_to(
+      "#{t("administrate.actions.edit")} #{page.page_title}",
+      [:edit, namespace, page.resource],
+      class: "button",
+    ) if valid_action? :edit %>
+  </div>
+</header>
+
+<section class="main-content__body">
+  <dl>
+    <% page.attributes.each do |attribute| %>
+      <dt class="attribute-label" id="<%= attribute.name %>">
+      <%= t(
+        "helpers.label.#{resource_name}.#{attribute.name}",
+        default: attribute.name.titleize,
+      ) %>
+      </dt>
+
+      <dd class="attribute-data attribute-data--<%=attribute.html_class%>"
+          ><%= render_field attribute %></dd>
+    <% end %>
+  </dl>
+</section>

+ 42 - 0
app/views/devise/registrations/edit.html.erb

@@ -0,0 +1,42 @@
+<div class="row">
+  <div class="col-lg-4 col-md-6 ml-auto mr-auto">
+    <h1 class="text-center">Account</h1>
+
+    <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
+      <%= devise_error_messages! %>
+
+      <div class="form-group">
+        <%= f.text_field :name, autofocus: false, class: 'form-control', placeholder: "Full Name" %>
+      </div>
+
+      <div class="form-group">
+        <%= f.email_field :email, class: 'form-control', placeholder: 'Email Address' %>
+      </div>
+
+      <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
+        <div class="alert alert-warning">Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
+      <% end %>
+
+      <div class="form-group">
+        <%= f.password_field :password, autocomplete: "off", class: 'form-control', placeholder: 'Password'  %>
+        <p class="form-text text-muted"><small>Leave password blank if you don't want to change it</small></p>
+      </div>
+
+      <div class="form-group">
+        <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control', placeholder: 'Confirm Password'  %>
+      </div>
+
+      <div class="form-group">
+        <%= f.password_field :current_password, autocomplete: "off", class: 'form-control', placeholder: 'Current Password'  %>
+        <p class="form-text text-muted"><small>We need your current password to confirm your changes</small></p>
+      </div>
+
+      <div class="form-group">
+        <%= f.submit "Save Changes", class: 'btn btn-lg btn-block btn-primary' %>
+      </div>
+    <% end %>
+    <hr>
+
+    <p class="text-center"><%= link_to "Deactivate my account", registration_path(resource_name), data: { confirm: "Are you sure? You cannot undo this." }, method: :delete %></p>
+  </div>
+</div>

+ 33 - 0
app/views/devise/registrations/new.html.erb

@@ -0,0 +1,33 @@
+<div class="row">
+  <div class="col-lg-4 col-md-6 ml-auto mr-auto">
+    <h1 class="text-center">Sign Up</h1>
+
+    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
+      <%= devise_error_messages! %>
+
+      <div class="form-group">
+        <%= f.text_field :name, autofocus: true, class: 'form-control', placeholder: "Full Name" %>
+      </div>
+
+      <div class="form-group">
+        <%= f.email_field :email, autofocus: false, class: 'form-control', placeholder: "Email Address" %>
+      </div>
+
+      <div class="form-group">
+        <%= f.password_field :password, autocomplete: "off", class: 'form-control', placeholder: 'Password' %>
+      </div>
+
+      <div class="form-group">
+        <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control', placeholder: 'Confirm Password' %>
+      </div>
+
+      <div class="form-group">
+        <%= f.submit "Sign up", class: "btn btn-primary btn-block btn-lg" %>
+      </div>
+    <% end %>
+
+    <div class="text-center">
+      <%= render "devise/shared/links" %>
+    </div>
+  </div>
+</div>

+ 2 - 0
app/views/home/index.html.erb

@@ -0,0 +1,2 @@
+<h1>Welcome to Jumpstart!</h1>
+<p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>

+ 2 - 0
app/views/home/privacy.html.erb

@@ -0,0 +1,2 @@
+<h1>Privacy Policy</h1>
+<p class="lead">Use this for your Privacy Policy</p>

+ 2 - 0
app/views/home/terms.html.erb

@@ -0,0 +1,2 @@
+<h1>Terms of Service</h1>
+<p class="lead">Use this for your Terms of Service</p>

+ 17 - 0
app/views/layouts/application.html.erb

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <%= render 'shared/head' %>
+  </head>
+
+  <body>
+    <%= render 'shared/navbar' %>
+    <%= render 'shared/notices' %>
+
+    <div class="container">
+      <%= yield %>
+    </div>
+
+    <%= render 'shared/footer' %>
+  </body>
+</html>

+ 9 - 0
app/views/shared/_footer.html.erb

@@ -0,0 +1,9 @@
+<footer class="footer text-muted bg-light">
+  <div class="container">
+    <span>© <%= Date.today.year %> Your Company</span>
+    <ul class="list-inline mb-0 float-right">
+      <li class="list-inline-item mr-3"><%= link_to "Terms", terms_path %></li>
+      <li class="list-inline-item mr-3"><%= link_to "Privacy", privacy_path %></li>
+    </ul>
+  </div>
+</footer>

+ 7 - 0
app/views/shared/_head.html.erb

@@ -0,0 +1,7 @@
+<title><%= Rails.configuration.application_name %></title>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+<%= csrf_meta_tags %>
+<%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
+<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

+ 51 - 0
app/views/shared/_navbar.html.erb

@@ -0,0 +1,51 @@
+<% if user_masquerade? %>
+  <div class="alert alert-warning text-center">
+    You're logged in as <b><%= current_user.name %> (<%= current_user.email %>)</b>
+    <%= link_to back_masquerade_path(current_user) do %><%= icon("fas", "times") %> Logout <% end %>
+  </div>
+<% end %>
+
+<nav class="navbar navbar-expand-md navbar-light bg-light">
+  <div class="container">
+    <%= link_to Rails.configuration.application_name, root_path, class: "navbar-brand" %>
+
+    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarMain" aria-controls="navbarsExample04" aria-expanded="false" aria-label="Toggle navigation">
+      <span class="navbar-toggler-icon"></span>
+    </button>
+
+    <div class="collapse navbar-collapse" id="navbarMain">
+      <ul class="navbar-nav mr-auto mt-2 mt-md-0">
+      </ul>
+
+      <ul class="navbar-nav">
+        <li class="nav-item"><%= link_to "What's New", announcements_path, class: "nav-link #{unread_announcements(current_user)}" %></li>
+        <% if user_signed_in? %>
+
+        <li class="nav-item">
+          <%= link_to notifications_path, class: "nav-link" do %>
+            <span><i class="fa fa-flag-o" aria-hidden="true"></i></span>
+          <% end %>
+         </li>
+
+          <li class="nav-item dropdown">
+            <%= link_to root_path, id: "navbar-dropdown", class: "nav-link dropdown-toggle", data: { toggle: "dropdown" }, aria: { haspopup: true, expanded: false } do %>
+              <%= image_tag gravatar_image_url(current_user.email, size: 40), height: 20, width: 20, class: "rounded" %>
+            <% end %>
+            <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbar-dropdown">
+              <% if current_user.admin? %>
+                <%= link_to "Admin Area", admin_root_path, class: "dropdown-item" %>
+              <% end %>
+              <%= link_to "Settings", edit_user_registration_path, class: "dropdown-item" %>
+              <div class="dropdown-divider"></div>
+              <%= link_to "Logout", destroy_user_session_path, method: :delete, class: "dropdown-item" %>
+            </div>
+          </li>
+
+        <% else %>
+          <li class="nav-item"><%= link_to "Sign Up", new_user_registration_path, class: "nav-link" %></li>
+          <li class="nav-item"><%= link_to "Login", new_user_session_path, class: "nav-link" %></li>
+        <% end %>
+      </ul>
+    </div>
+  </div>
+</nav>

+ 8 - 0
app/views/shared/_notices.html.erb

@@ -0,0 +1,8 @@
+<% flash.each do |msg_type, message| %>
+  <div class="alert <%= bootstrap_class_for(msg_type) %>">
+    <div class="container">
+      <button class="close" data-dismiss="alert"><span>×</span></button>
+      <%= message %>
+    </div>
+  </div>
+<% end %>

+ 13 - 0
config/cable.yml

@@ -0,0 +1,13 @@
+development:
+  adapter: redis
+  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
+  channel_prefix: streaming_logs_dev
+
+test:
+  adapter: async
+
+production:
+  adapter: redis
+  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
+  channel_prefix: streaming_logs_production
+

+ 4 - 0
config/initializers/gravatar.rb

@@ -0,0 +1,4 @@
+GravatarImageTag.configure do |config|
+  config.default_image = "mm"
+  config.secure        = true
+end

+ 46 - 0
lib/templates/erb/scaffold/_form.html.erb

@@ -0,0 +1,46 @@
+<%%= form_with(model: <%= model_resource_name %>, local: true) do |form| %>
+  <%% if <%= singular_table_name %>.errors.any? %>
+    <div id="error_explanation">
+      <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
+
+      <ul>
+      <%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
+        <li><%%= message %></li>
+      <%% end %>
+      </ul>
+    </div>
+  <%% end %>
+
+<% attributes.each do |attribute| -%>
+  <div class="form-group">
+<% if attribute.password_digest? -%>
+    <%%= form.label :password %>
+    <%%= form.password_field :password, class: 'form-control' %>
+  </div>
+
+  <div class="form-group">
+    <%%= form.label :password_confirmation %>
+    <%%= form.password_field :password_confirmation, class: 'form-control' %>
+<% else -%>
+    <%%= form.label :<%= attribute.column_name %> %>
+    <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: 'form-control' %>
+<% end -%>
+  </div>
+
+<% end -%>
+  <div class="form-group">
+    <%% if <%= model_resource_name %>.persisted? %>
+      <div class="float-right">
+        <%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, class: "text-danger", data: { confirm: 'Are you sure?' } %>
+      </div>
+    <%% end %>
+
+    <%%= form.submit class: 'btn btn-primary' %>
+
+    <%% if <%= model_resource_name %>.persisted? %>
+      <%%= link_to "Cancel", <%= model_resource_name %>, class: "btn btn-link" %>
+    <%% else %>
+      <%%= link_to "Cancel", <%= index_helper %>_path, class: "btn btn-link" %>
+    <%% end %>
+  </div>
+<%% end %>

+ 3 - 0
lib/templates/erb/scaffold/edit.html.erb

@@ -0,0 +1,3 @@
+<h1>Edit <%= singular_table_name.capitalize %></h1>
+
+<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>

+ 51 - 0
lib/templates/erb/scaffold/index.html.erb

@@ -0,0 +1,51 @@
+<% name_attribute = attributes.find{ |a| a.name == "name" } %>
+<% has_name = !!name_attribute %>
+
+<div class="row">
+  <div class="col-sm-6">
+    <h1><%= plural_table_name.capitalize %></h1>
+  </div>
+
+  <div class="col-sm-6 text-right">
+  <%%= link_to new_<%= singular_table_name %>_path, class: 'btn btn-primary' do %>
+    Add New <%= human_name %>
+  <%% end %>
+  </div>
+</div>
+
+<div class="table-responsive">
+  <table class="table table-striped table-bordered table-hover">
+    <thead>
+      <tr>
+    <% if has_name %>
+        <th>Name</th>
+    <% end %>
+
+    <% attributes.without(name_attribute).each do |attribute| -%>
+        <th><%= attribute.human_name %></th>
+    <% end -%>
+        <% unless has_name %>
+          <th></th>
+        <% end %>
+      </tr>
+    </thead>
+
+    <tbody>
+      <%% @<%= plural_table_name%>.each do |<%= singular_table_name %>| %>
+        <%%= content_tag :tr, id: dom_id(<%= singular_table_name %>), class: dom_class(<%= singular_table_name %>) do %>
+          <% if has_name %>
+            <td><%%= link_to <%= singular_table_name %>.name, <%= singular_table_name %> %></td>
+          <% end %>
+
+          <% attributes.without(name_attribute).each do |attribute| -%>
+            <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+          <% end -%>
+
+          <% unless has_name %>
+            <td><%%= link_to 'Show', <%= singular_table_name %> %></td>
+          <% end %>
+        <%% end %>
+      <%% end %>
+    </tbody>
+  </table>
+</div>

+ 3 - 0
lib/templates/erb/scaffold/new.html.erb

@@ -0,0 +1,3 @@
+<h1>New <%= singular_table_name %></h1>
+
+<%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>

+ 19 - 0
lib/templates/erb/scaffold/show.html.erb

@@ -0,0 +1,19 @@
+<div class="page-header">
+  <%%= link_to <%= index_helper %>_path, class: 'btn btn-default' do %>
+    <span class="glyphicon glyphicon-list-alt"></span>
+    All <%= plural_table_name.capitalize %>
+  <%% end %>
+  <%%= link_to edit_<%= singular_table_name %>_path(@<%= singular_table_name %>), class: 'btn btn-primary' do %>
+    <span class="glyphicon glyphicon-pencil"></span>
+    Edit
+  <%% end %>
+  <h1>Show <%= singular_table_name %></h1>
+</div>
+
+<dl class="dl-horizontal">
+  <%- attributes.each do |attribute| -%>
+  <dt><%= attribute.human_name %>:</dt>
+  <dd><%%= @<%= singular_table_name %>.<%= attribute.name %> %></dd>
+
+  <%- end -%>
+</dl>

+ 188 - 0
template.rb

@@ -0,0 +1,188 @@
+require "fileutils"
+require "shellwords"
+
+# Copied from: https://github.com/mattbrictson/rails-template
+# Add this template directory to source_paths so that Thor actions like
+# copy_file and template resolve against our source files. If this file was
+# invoked remotely via HTTP, that means the files are not present locally.
+# In that case, use `git clone` to download them to a local temporary dir.
+def add_template_repository_to_source_path
+  if __FILE__ =~ %r{\Ahttps?://}
+    require "tmpdir"
+    source_paths.unshift(tempdir = Dir.mktmpdir("jumpstart-"))
+    at_exit { FileUtils.remove_entry(tempdir) }
+    git clone: [
+      "--quiet",
+      "https://github.com/excid3/jumpstart.git",
+      tempdir
+    ].map(&:shellescape).join(" ")
+
+    if (branch = __FILE__[%r{jumpstart/(.+)/template.rb}, 1])
+      Dir.chdir(tempdir) { git checkout: branch }
+    end
+  else
+    source_paths.unshift(File.dirname(__FILE__))
+  end
+end
+
+def add_gems
+  gem 'bootstrap', '~> 4.1', '>= 4.1.1'
+  gem 'devise', '~> 4.5', '>= 4.4.3'
+  gem 'devise-bootstrapped', github: 'excid3/devise-bootstrapped', branch: 'bootstrap4'
+  gem 'font-awesome-sass', '~> 5.0', '>= 5.0.13'
+  gem 'gravatar_image_tag', github: 'mdeering/gravatar_image_tag'
+  gem 'jquery-rails', '~> 4.3.1'
+  gem 'mini_magick', '~> 4.8'
+  gem 'omniauth-facebook', '~> 5.0'
+  gem 'omniauth-github', '~> 1.3'
+  gem 'omniauth-twitter', '~> 1.4'
+  gem 'sidekiq', '~> 5.1', '>= 5.1.3'
+  gem 'webpacker', '~> 3.5', '>= 3.5.3'
+  gem 'procodile', '~> 1.0', '>= 1.0.16'
+end
+
+def set_application_name
+  # Add Application Name to Config
+  environment "config.application_name = Rails.application.class.parent_name"
+
+  # Announce the user where he can change the application name in the future.
+  puts "You can change application name inside: ./config/application.rb"
+end
+
+def add_users
+  # Install Devise
+  generate "devise:install"
+
+  # Configure Devise
+  environment "config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }",
+              env: 'development'
+  route "root to: 'home#index'"
+
+  # Devise notices are installed via Bootstrap
+  generate "devise:views:bootstrapped"
+
+  # Create Devise User
+  generate :devise, "User",
+           "first_name",
+           "last_name",
+           "announcements_last_read_at:datetime",
+           "admin:boolean"
+
+  # Set admin default to false
+  in_root do
+    migration = Dir.glob("db/migrate/*").max_by{ |f| File.mtime(f) }
+    gsub_file migration, /:admin/, ":admin, default: false"
+  end
+
+  requirement = Gem::Requirement.new("> 5.2")
+  rails_version = Gem::Version.new(Rails::VERSION::STRING)
+
+  if requirement.satisfied_by? rails_version
+    gsub_file "config/initializers/devise.rb",
+      /  # config.secret_key = .+/,
+      "  config.secret_key = Rails.application.credentials.secret_key_base"
+  end
+
+  # Add Devise masqueradable to users
+  inject_into_file("app/models/user.rb", "omniauthable, :masqueradable, :", after: "devise :")
+end
+
+def add_bootstrap
+  # Remove Application CSS
+  run "rm app/assets/stylesheets/application.css"
+
+  # Add Bootstrap JS
+  insert_into_file(
+    "app/assets/javascripts/application.js",
+    "\n//= require jquery\n//= require popper\n//= require bootstrap\n//= require data-confirm-modal\n//= require local-time",
+    after: "//= require rails-ujs"
+  )
+end
+
+def copy_templates
+  directory "app", force: true
+  directory "config", force: true
+  directory "lib", force: true
+
+  route "get '/terms', to: 'home#terms'"
+  route "get '/privacy', to: 'home#privacy'"
+end
+
+def add_webpack
+  rails_command 'webpacker:install'
+  rails_command 'webpacker:install:stimulus'
+end
+
+def add_sidekiq
+  environment "config.active_job.queue_adapter = :sidekiq"
+
+  insert_into_file "config/routes.rb",
+    "require 'sidekiq/web'\n\n",
+    before: "Rails.application.routes.draw do"
+
+  insert_into_file "config/routes.rb",
+    "  authenticate :user, lambda { |u| u.admin? } do\n    mount Sidekiq::Web => '/sidekiq'\n  end\n\n",
+    after: "Rails.application.routes.draw do\n"
+end
+
+def add_procodile
+  copy_file "Procfile"
+end
+
+def add_multiple_authentication
+    insert_into_file "config/routes.rb",
+    ', controllers: { omniauth_callbacks: "users/omniauth_callbacks" }',
+    after: "  devise_for :users"
+
+    generate "model Service user:references provider uid access_token access_token_secret refresh_token expires_at:datetime auth:text"
+
+    template = """
+  if Rails.application.secrets.facebook_app_id.present? && Rails.application.secrets.facebook_app_secret.present?
+    config.omniauth :facebook, Rails.application.secrets.facebook_app_id, Rails.application.secrets.facebook_app_secret, scope: 'email,user_posts'
+  end
+
+  if Rails.application.secrets.twitter_app_id.present? && Rails.application.secrets.twitter_app_secret.present?
+    config.omniauth :twitter, Rails.application.secrets.twitter_app_id, Rails.application.secrets.twitter_app_secret
+  end
+
+  if Rails.application.secrets.github_app_id.present? && Rails.application.secrets.github_app_secret.present?
+    config.omniauth :github, Rails.application.secrets.github_app_id, Rails.application.secrets.github_app_secret
+  end
+    """.strip
+
+    insert_into_file "config/initializers/devise.rb", "  " + template + "\n\n",
+          before: "  # ==> Warden configuration"
+end
+
+
+def stop_spring
+  run "spring stop"
+end
+
+# Main setup
+add_template_repository_to_source_path
+
+add_gems
+
+after_bundle do
+  set_application_name
+  stop_spring
+  add_users
+  add_bootstrap
+  add_sidekiq
+  add_procodile
+  add_webpack
+  add_multiple_authentication
+
+  copy_templates
+
+  # Migrate
+  rails_command "db:create"
+  rails_command "db:migrate"
+
+  # Migrations must be done before this
+
+  git :init
+  git add: "."
+  git commit: %Q{ -m 'Initial commit' }
+end