next_on_rails 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ })