next_on_rails 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +591 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/next_on_rails/application_controller.rb +21 -0
  6. data/app/controllers/next_on_rails/devise_token_auth_override/concerns/current_resource_serializer.rb +15 -0
  7. data/app/controllers/next_on_rails/devise_token_auth_override/confirmations_controller.rb +4 -0
  8. data/app/controllers/next_on_rails/devise_token_auth_override/omniauth_callbacks_controller.rb +7 -0
  9. data/app/controllers/next_on_rails/devise_token_auth_override/passwords_controller.rb +23 -0
  10. data/app/controllers/next_on_rails/devise_token_auth_override/registrations_controller.rb +33 -0
  11. data/app/controllers/next_on_rails/devise_token_auth_override/sessions_controller.rb +27 -0
  12. data/app/controllers/next_on_rails/devise_token_auth_override/token_validations_controller.rb +13 -0
  13. data/app/controllers/next_on_rails/devise_token_auth_override/unlocks_controller.rb +23 -0
  14. data/app/controllers/next_on_rails/merger_controller.rb +36 -0
  15. data/app/serializers/next_on_rails/active_model_errors_serializer.rb +45 -0
  16. data/app/serializers/next_on_rails/errors_serializer.rb +16 -0
  17. data/config/routes.rb +12 -0
  18. data/lib/generators/nor/backend/backend_generator.rb +129 -0
  19. data/lib/generators/nor/backend/templates/Procfile +2 -0
  20. data/lib/generators/nor/backend/templates/application_controller.rb +2 -0
  21. data/lib/generators/nor/backend/templates/cors.rb +23 -0
  22. data/lib/generators/nor/backend/templates/current_user_serializer.rb.tt +8 -0
  23. data/lib/generators/nor/backend/templates/user.rb.tt +8 -0
  24. data/lib/generators/nor/backend/templates/user_migration.rb.tt +53 -0
  25. data/lib/generators/nor/backend/templates/user_serializer.rb.tt +11 -0
  26. data/lib/generators/nor/frontend/frontend_generator.rb +60 -0
  27. data/lib/generators/nor/frontend/templates/frontend/components/flash.js +29 -0
  28. data/lib/generators/nor/frontend/templates/frontend/components/inputs.js +69 -0
  29. data/lib/generators/nor/frontend/templates/frontend/components/login-form.js +23 -0
  30. data/lib/generators/nor/frontend/templates/frontend/components/registration-form.js +49 -0
  31. data/lib/generators/nor/frontend/templates/frontend/next-on-rails.config.js +3 -0
  32. data/lib/generators/nor/frontend/templates/frontend/next.config.js +20 -0
  33. data/lib/generators/nor/frontend/templates/frontend/pages/_app.js +3 -0
  34. data/lib/generators/nor/frontend/templates/frontend/pages/index.js +57 -0
  35. data/lib/generators/nor/frontend/templates/frontend/server.js +31 -0
  36. data/lib/generators/nor/frontend/templates/frontend/stylesheets/application.scss +2 -0
  37. data/lib/generators/nor/install/USAGE +30 -0
  38. data/lib/generators/nor/install/install_generator.rb +8 -0
  39. data/lib/generators/nor/scaffold/USAGE +10 -0
  40. data/lib/generators/nor/scaffold/scaffold_generator.rb +53 -0
  41. data/lib/generators/nor/scaffold/templates/controller.rb.tt +40 -0
  42. data/lib/generators/nor/scaffold/templates/frontend/components/details.js.tt +20 -0
  43. data/lib/generators/nor/scaffold/templates/frontend/components/form.js.tt +17 -0
  44. data/lib/generators/nor/scaffold/templates/frontend/pages/crud.js.tt +123 -0
  45. data/lib/generators/nor/scaffold/templates/frontend/pages/edit.js.tt +55 -0
  46. data/lib/generators/nor/scaffold/templates/frontend/pages/index.js.tt +88 -0
  47. data/lib/generators/nor/scaffold/templates/frontend/pages/new.js.tt +51 -0
  48. data/lib/generators/nor/scaffold/templates/frontend/pages/show.js.tt +43 -0
  49. data/lib/next_on_rails.rb +12 -0
  50. data/lib/next_on_rails/engine.rb +5 -0
  51. data/lib/next_on_rails/version.rb +3 -0
  52. data/lib/tasks/next_on_rails_tasks.rake +4 -0
  53. metadata +235 -0
@@ -0,0 +1,2 @@
1
+ backend: bundle exec rails s -p 3000
2
+ frontend: sh -c 'cd ./frontend && yarn run dev'
@@ -0,0 +1,2 @@
1
+ class ApplicationController < NextOnRails::ApplicationController
2
+ end
@@ -0,0 +1,23 @@
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 'localhost:3001'
11
+
12
+ resource '*',
13
+ headers: :any,
14
+ methods: [:get, :post, :put, :patch, :delete, :options, :head],
15
+ expose: [
16
+ DeviseTokenAuth.headers_names[:'access-token'],
17
+ DeviseTokenAuth.headers_names[:client],
18
+ DeviseTokenAuth.headers_names[:expiry],
19
+ DeviseTokenAuth.headers_names[:uid],
20
+ DeviseTokenAuth.headers_names[:'token-type']
21
+ ]
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ class Current<%= user_class %>Serializer < <%= user_class %>Serializer
2
+ attributes :email
3
+ meta do |current_<%= user_class.underscore %>|
4
+ {
5
+ last_sign_in_at: current_<%= user_class.underscore %>.last_sign_in_at
6
+ }
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ class <%= user_class %> < ApplicationRecord
2
+ # Include default devise modules. Others available are:
3
+ # :timeoutable
4
+ devise :database_authenticatable, :registerable,
5
+ :recoverable, :rememberable, :trackable, :validatable,
6
+ :confirmable, :lockable, :omniauthable
7
+ include DeviseTokenAuth::Concerns::<%= user_class %>
8
+ end
@@ -0,0 +1,53 @@
1
+ class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table(:<%= user_class.constantize.table_name %>) do |t|
4
+ ## User Info
5
+ t.string :email
6
+
7
+ ## Required
8
+ t.string :provider, null: false, default: 'email'
9
+ t.string :uid, null: false, default: ''
10
+
11
+ ## Database authenticatable
12
+ t.string :encrypted_password, null: false, default: ''
13
+
14
+ ## Recoverable
15
+ t.string :reset_password_token
16
+ t.datetime :reset_password_sent_at
17
+ t.boolean :allow_password_change, default: false
18
+
19
+ ## Rememberable
20
+ t.datetime :remember_created_at
21
+
22
+ ## Trackable
23
+ t.integer :sign_in_count, default: 0, null: false
24
+ t.datetime :current_sign_in_at
25
+ t.datetime :last_sign_in_at
26
+ t.string :current_sign_in_ip
27
+ t.string :last_sign_in_ip
28
+
29
+ ## Confirmable
30
+ t.string :confirmation_token
31
+ t.datetime :confirmed_at
32
+ t.datetime :confirmation_sent_at
33
+ t.string :unconfirmed_email # Only if using reconfirmable
34
+
35
+ ## Lockable
36
+ t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
37
+ t.string :unlock_token # Only if unlock strategy is :email or :both
38
+ t.datetime :locked_at
39
+
40
+ ## Tokens
41
+ t.text :tokens
42
+
43
+ t.timestamps
44
+ end
45
+
46
+ add_index :users, :email, unique: true
47
+ add_index :users, :uid, unique: true
48
+ add_index :users, [:uid, :provider], unique: true
49
+ add_index :users, :reset_password_token, unique: true
50
+ add_index :users, :confirmation_token, unique: true
51
+ add_index :users, :unlock_token, unique: true
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ class <%= user_class %>Serializer
2
+ include FastJsonapi::ObjectSerializer
3
+
4
+ set_type :<%= user_class.underscore %>
5
+ set_id :id
6
+ attributes :email
7
+
8
+ # link :<%= user_class.underscore %>_url do |<%= user_class.underscore %>|
9
+ # Rails.application.routes.url_helpers.<%= user_class.underscore %>_url(<%= user_class.underscore %>, host: 'http://localhost:3000')
10
+ # end
11
+ end
@@ -0,0 +1,60 @@
1
+ module Nor
2
+ class FrontendGenerator < Rails::Generators::Base
3
+ attr_reader :frontend_path
4
+ source_root File.expand_path('templates', __dir__)
5
+
6
+ def initialize(args, *options)
7
+ super
8
+ @frontend_path = File.join(destination_root, 'frontend')
9
+ end
10
+
11
+ def create_next_app
12
+ if revoke? && frontend_exist?
13
+ say_status :remove, relative_to_original_destination_root(frontend_path)
14
+ FileUtils.remove_dir(frontend_path, force: true)
15
+ elsif frontend_exist?
16
+ say_status('skipped', 'Create Next app: directory frontend already exist!')
17
+ else
18
+ run 'yarn create next-app frontend'
19
+ inside frontend_path do
20
+ gsub_file 'package.json', 'create-next-example-app', 'next-on-rails-frontend'
21
+ gsub_file 'package.json', '"dev": "next dev"', '"dev": "node server.js"'
22
+ if `which json`.present?
23
+ run "json -I -f package.json -e 'this.license=\"MIT\"'"
24
+ run "json -I -f package.json -e 'this.private=true'"
25
+ run "json -I -f package.json -e 'this.version=\"0.1.0\"'"
26
+ end
27
+ remove_file 'components/head.js'
28
+ remove_file 'components/nav.js'
29
+ remove_file 'pages/index.js'
30
+ remove_file 'next.config.js'
31
+ end
32
+ end
33
+ end
34
+
35
+ def install_dependencies
36
+ return if revoke?
37
+
38
+ inside frontend_path do
39
+ run 'yarn add next-compose-plugins @zeit/next-sass node-sass bootstrap compression express inflection lodash nookies nprogress next-on-rails next@8.1.0 react@16.8.6 react-dom@16.8.6'
40
+ run 'yarn add --dev babel-eslint eslint eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-react eslint-plugin-standard prettier sass-lint'
41
+ end
42
+ end
43
+
44
+ def install_templates
45
+ return if revoke?
46
+
47
+ directory 'frontend'
48
+ end
49
+
50
+ protected
51
+
52
+ def frontend_exist?
53
+ File.exist?(frontend_path)
54
+ end
55
+
56
+ def revoke?
57
+ behavior == :revoke
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,29 @@
1
+ import { useFlash } from 'next-on-rails/utils'
2
+
3
+ const Flash = () => {
4
+ const { flash } = useFlash()
5
+ if (flash) {
6
+ return Object.entries(flash)
7
+ .filter(([key, value]) => value && value !== '')
8
+ .map(([key, value]) => (
9
+ <div key={key} className={`alert alert-${alertClass(key)}`}>
10
+ {value}
11
+ </div>
12
+ ))
13
+ } else {
14
+ return null
15
+ }
16
+ }
17
+
18
+ const alertClass = flashType => {
19
+ switch (flashType) {
20
+ case 'notice':
21
+ return 'success'
22
+ case 'alert':
23
+ return 'danger'
24
+ default:
25
+ return 'info'
26
+ }
27
+ }
28
+
29
+ export default Flash
@@ -0,0 +1,69 @@
1
+ import { dasherize, titleize } from 'inflection'
2
+
3
+ const Input = ({ name, value, label, error }) => {
4
+ const id = `input-${dasherize(name).toLowerCase()}`
5
+ const className = `form-control ${error ? 'is-invalid' : null}`
6
+ if (label === undefined) label = titleize(name)
7
+ return (
8
+ <div className="form-group">
9
+ {label ? <label htmlFor={id}>{label}</label> : null}
10
+ <input type="text" id={id} className={className} name={name} defaultValue={value} />
11
+ {error ? <div className="invalid-feedback">{error}</div> : null}
12
+ </div>
13
+ )
14
+ }
15
+
16
+ const TextArea = ({ name, value, label, error }) => {
17
+ const id = `input-${dasherize(name).toLowerCase()}`
18
+ const className = `form-control ${error ? 'is-invalid' : null}`
19
+ if (label === undefined) label = titleize(name)
20
+ return (
21
+ <div className="form-group">
22
+ {label ? <label htmlFor={id}>{label}</label> : null}
23
+ <textarea id={id} className={className} name={name} defaultValue={value}></textarea>
24
+ {error ? <div className="invalid-feedback">{error}</div> : null}
25
+ </div>
26
+ )
27
+ }
28
+
29
+ const CheckBox = ({ name, value, label, error }) => {
30
+ const id = `input-${dasherize(name).toLowerCase()}`
31
+ const className = `form-check-input ${error ? 'is-invalid' : null}`
32
+ if (label === undefined) label = titleize(name)
33
+ return (
34
+ <div className="form-group form-check">
35
+ <input type="hidden" name={name} defaultValue={false} />
36
+ <input type="checkbox" id={id} className={className} name={name} defaultChecked={value} />
37
+ {error ? <div className="invalid-feedback">{error}</div> : null}
38
+ {label ? (
39
+ <label htmlFor={id} className="form-post-label">
40
+ {label}
41
+ </label>
42
+ ) : null}
43
+ </div>
44
+ )
45
+ }
46
+
47
+ const Select = ({ name, value, label, error, collection, selectPlaceholder = 'Select one...' }) => {
48
+ const id = `input-${dasherize(name).toLowerCase()}`
49
+ const className = `form-control ${error ? 'is-invalid' : null}`
50
+ if (label === undefined) label = titleize(name)
51
+ return (
52
+ <div className="form-group">
53
+ {label ? <label htmlFor={id}>{label}</label> : null}
54
+ <select id={id} className={className} name={name} defaultValue={value}>
55
+ {selectPlaceholder ? <option>{selectPlaceholder}</option> : null}
56
+ {collection.map(([value, display]) => (
57
+ <option key={value} value={value}>
58
+ {display}
59
+ </option>
60
+ ))}
61
+ </select>
62
+ {error ? <div className="invalid-feedback">{error}</div> : null}
63
+ </div>
64
+ )
65
+ }
66
+
67
+ const HiddenIdField = ({ id }) => (id ? <input type="hidden" name="id" defaultValue={id} /> : null)
68
+
69
+ export { Input, TextArea, CheckBox, Select, HiddenIdField }
@@ -0,0 +1,23 @@
1
+ import { useLoginForm } from 'next-on-rails/devise'
2
+
3
+ const LoginForm = ({ successCallback, errorCallback }) => {
4
+ const login = useLoginForm(successCallback, errorCallback)
5
+
6
+ return (
7
+ <form onSubmit={login}>
8
+ <div className="form-group">
9
+ <label htmlFor="form-login-email">Email</label>
10
+ <input id="form-login-email" type="text" name="email" className="form-control" />
11
+ </div>
12
+ <div className="form-group">
13
+ <label htmlFor="form-login-password">Password</label>
14
+ <input id="form-login-password" type="password" name="password" className="form-control" />
15
+ </div>
16
+ <button type="submit" className="btn btn-primary">
17
+ Login
18
+ </button>
19
+ </form>
20
+ )
21
+ }
22
+
23
+ export default LoginForm
@@ -0,0 +1,49 @@
1
+ import { useRegistrationForm } from 'next-on-rails/devise'
2
+
3
+ const RegistrationForm = ({ successCallback, errorCallback }) => {
4
+ const [register, getError] = useRegistrationForm(successCallback, errorCallback)
5
+
6
+ return (
7
+ <form onSubmit={register}>
8
+ <div className="form-group">
9
+ <label htmlFor="form-registration-email">Email</label>
10
+ <input
11
+ id="form-registration-email"
12
+ type="text"
13
+ name="email"
14
+ className={`form-control ${getError('email') ? 'is-invalid' : null}`}
15
+ />
16
+ {getError('email') ? <div className="invalid-feedback">{getError('email')}</div> : null}
17
+ </div>
18
+ <div className="form-group">
19
+ <label htmlFor="form-registration-password">Password</label>
20
+ <input
21
+ id="form-registration-password"
22
+ type="password"
23
+ name="password"
24
+ className={`form-control ${getError('password') ? 'is-invalid' : null}`}
25
+ defaultValue="qwerty"
26
+ />
27
+ {getError('password') ? <div className="invalid-feedback">{getError('password')}</div> : null}
28
+ </div>
29
+ <div className="form-group">
30
+ <label htmlFor="form-registration-password-confirmation">Confirm Password</label>
31
+ <input
32
+ id="form-registration-password-confirmation"
33
+ type="password"
34
+ name="password_confirmation"
35
+ className={`form-control ${getError('password') ? 'is-invalid' : null}`}
36
+ defaultValue="qwerty"
37
+ />
38
+ {getError('password_confirmation') ? (
39
+ <div className="invalid-feedback">{getError('password_confirmation')}</div>
40
+ ) : null}
41
+ </div>
42
+ <button type="submit" className="btn btn-primary">
43
+ Register
44
+ </button>
45
+ </form>
46
+ )
47
+ }
48
+
49
+ export default RegistrationForm
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ baseURL: 'http://localhost:3000'
3
+ }
@@ -0,0 +1,20 @@
1
+ const withPlugins = require('next-compose-plugins')
2
+ const withSass = require('@zeit/next-sass')
3
+ // const optimizedImages = require('next-optimized-images')
4
+
5
+ module.exports = withPlugins(
6
+ [
7
+ [withSass, {}]
8
+ // [optimizedImages, {}],
9
+ // [exportPathMap, {
10
+ // '/': { page: '/' }
11
+ // }]
12
+ ],
13
+ {
14
+ webpack: config => {
15
+ // Fixes npm packages that depend on `fs` module
16
+ config.node = { fs: 'empty' }
17
+ return config
18
+ }
19
+ }
20
+ )
@@ -0,0 +1,3 @@
1
+ import '../stylesheets/application.scss'
2
+ import App from 'next-on-rails/app'
3
+ export default App
@@ -0,0 +1,57 @@
1
+ import { useCurrentUser } from 'next-on-rails/current-user'
2
+ import { useLogout } from 'next-on-rails/devise'
3
+ import { getDetailFromResponseError, useFlash } from 'next-on-rails/utils'
4
+
5
+ import Flash from '../components/flash'
6
+ import LoginForm from '../components/login-form'
7
+ import RegistrationForm from '../components/registration-form'
8
+
9
+ const Home = props => {
10
+ const { setFlash } = useFlash()
11
+ const logout = useLogout()
12
+ const { currentUser } = useCurrentUser()
13
+
14
+ const loginSuccessCallback = response => {
15
+ setFlash({ notice: `Welcome ${response.data.email}!` })
16
+ }
17
+
18
+ const loginErrorCallback = error => {
19
+ setFlash({ alert: getDetailFromResponseError(error) })
20
+ }
21
+
22
+ const registrationSuccessCallback = response => {
23
+ setFlash({
24
+ notice: `Welcome ${response.data.email}! An email to confirm your account has been sent to this email address.`
25
+ })
26
+ }
27
+
28
+ return (
29
+ <div className="container pt-4">
30
+ <Flash />
31
+ <h1>Welcome to Next On Rails!</h1>
32
+
33
+ <img src="https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif" alt="Magic" />
34
+
35
+ <p>{currentUser ? `Hi, ${currentUser.email}` : 'You are not logged'}</p>
36
+
37
+ {currentUser ? (
38
+ <button className="btn btn-primary" onClick={logout}>
39
+ Logout
40
+ </button>
41
+ ) : (
42
+ <div className="row">
43
+ <div className="col">
44
+ <h6>Login</h6>
45
+ <LoginForm successCallback={loginSuccessCallback} errorCallback={loginErrorCallback} />
46
+ </div>
47
+ <div className="col">
48
+ <h6>Register</h6>
49
+ <RegistrationForm successCallback={registrationSuccessCallback} />
50
+ </div>
51
+ </div>
52
+ )}
53
+ </div>
54
+ )
55
+ }
56
+
57
+ export default Home
@@ -0,0 +1,31 @@
1
+ const express = require('express')
2
+ const next = require('next')
3
+ const compression = require('compression')
4
+
5
+ const dev = process.env.NODE_ENV !== 'production'
6
+ const app = next({ dev })
7
+ const handle = app.getRequestHandler()
8
+
9
+ app
10
+ .prepare()
11
+ .then(() => {
12
+ const server = express()
13
+ server.use(compression())
14
+
15
+ // server.get('/post/:id', (req, res) => {
16
+ // app.render(req, res, '/post', { id: req.params.id })
17
+ // })
18
+
19
+ server.get('*', (req, res) => {
20
+ return handle(req, res)
21
+ })
22
+
23
+ server.listen(3001, err => {
24
+ if (err) throw err
25
+ console.log('> Ready on http://localhost:3001')
26
+ })
27
+ })
28
+ .catch(ex => {
29
+ console.error(ex.stack)
30
+ process.exit(1)
31
+ })