diff --git a/.gitignore b/.gitignore index d31efa22..3ce6fe7f 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,13 @@ build-iPhoneSimulator/ /gemfiles/vendor/bundle /lib/bundler/man/ +# Ignore webpacker files +/spec/rails_app/node_modules +/spec/rails_app/yarn.lock +/spec/rails_app/yarn-error.log +/spec/rails_app/public/packs +/spec/rails_app/public/packs-test + # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # Gemfile.lock diff --git a/Gemfile b/Gemfile index 72c1cfad..0c60f2d2 100644 --- a/Gemfile +++ b/Gemfile @@ -25,5 +25,6 @@ group :test do gem 'coveralls', require: false end +gem 'webpacker', groups: [:production, :development] gem 'rack-cors', groups: [:production, :development] gem 'dotenv-rails', groups: [:development, :test] diff --git a/README.md b/README.md index c42f0fd5..afcfc392 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,11 @@ The deployed demo application is included in this gem's source code as a test ap REST API reference as OpenAPI Specification is published in SwaggerHub here: * **https://app.swaggerhub.com/apis/simukappu/activity-notification/** +You can see sample single page application using [Vue.js](https://vuejs.org) as a part of example Rails application here: +* **https://activity-notification-example.herokuapp.com/spa/** + +This sample application works with *activity_notification* REST API backend. + ## Table of Contents diff --git a/bin/deploy_on_heroku.sh b/bin/deploy_on_heroku.sh index 234e0bfd..c4651f33 100755 --- a/bin/deploy_on_heroku.sh +++ b/bin/deploy_on_heroku.sh @@ -6,9 +6,11 @@ CURRENT_BRANCH=`git symbolic-ref --short HEAD` git checkout -b $HEROKU_DEPLOYMENT_BRANCH bundle install sed -i "" -e "s/^\/Gemfile.lock/# \/Gemfile.lock/g" .gitignore +cp spec/rails_app/bin/webpack* bin/ git add .gitignore git add Gemfile.lock -git commit -m "Add Gemfile.lock" +git add bin/webpack* +git commit -m "Add Gemfile.lock and webpack" git push heroku ${HEROKU_DEPLOYMENT_BRANCH}:master --force git checkout $CURRENT_BRANCH git branch -D $HEROKU_DEPLOYMENT_BRANCH diff --git a/docs/Functions.md b/docs/Functions.md index 6038b23b..9b821e5c 100644 --- a/docs/Functions.md +++ b/docs/Functions.md @@ -385,6 +385,8 @@ Then, *activity_notification* uses *[ActivityNotification::SubscriptionsApiContr When you want to use REST API backend integrated with Devise authentication, see [REST API backend with Devise Token Auth](#rest-api-backend-with-devise-token-auth). +You can see [sample single page application](/spec/rails_app/app/javascript/) using [Vue.js](https://vuejs.org) as a part of example Rails application. This sample application works with *activity_notification* REST API backend. + #### API reference as OpenAPI Specification *activity_notification* provides API reference as [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification). diff --git a/package.json b/package.json new file mode 100644 index 00000000..3d44be60 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "engines": { + "yarn": "1.x" + }, + "scripts": { + "postinstall": "cd ./spec/rails_app && yarn && yarn install --check-files" + } +} diff --git a/spec/rails_app/app/controllers/admins_controller.rb b/spec/rails_app/app/controllers/admins_controller.rb new file mode 100644 index 00000000..463775b8 --- /dev/null +++ b/spec/rails_app/app/controllers/admins_controller.rb @@ -0,0 +1,21 @@ +class AdminsController < ApplicationController + before_action :set_admin, only: [:show] + + # GET /users + def index + render json: { + users: Admin.all.as_json(include: :user) + } + end + + # GET /users/:id + def show + render json: @admin.as_json(include: :user) + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_admin + @admin = Admin.find(params[:id]) + end +end diff --git a/spec/rails_app/app/controllers/spa_controller.rb b/spec/rails_app/app/controllers/spa_controller.rb new file mode 100644 index 00000000..baab0d0b --- /dev/null +++ b/spec/rails_app/app/controllers/spa_controller.rb @@ -0,0 +1,7 @@ +class SpaController < ApplicationController + + # GET /spa + def index + end + +end \ No newline at end of file diff --git a/spec/rails_app/app/controllers/users_controller.rb b/spec/rails_app/app/controllers/users_controller.rb new file mode 100644 index 00000000..5d1801b3 --- /dev/null +++ b/spec/rails_app/app/controllers/users_controller.rb @@ -0,0 +1,21 @@ +class UsersController < ApplicationController + before_action :set_user, only: [:show] + + # GET /users + def index + render json: { + users: User.all + } + end + + # GET /users/:id + def show + render json: @user + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_user + @user = User.find(params[:id]) + end +end diff --git a/spec/rails_app/app/javascript/App.vue b/spec/rails_app/app/javascript/App.vue new file mode 100644 index 00000000..65dc0551 --- /dev/null +++ b/spec/rails_app/app/javascript/App.vue @@ -0,0 +1,93 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue b/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue new file mode 100644 index 00000000..06e1a9b3 --- /dev/null +++ b/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue @@ -0,0 +1,83 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/Top.vue b/spec/rails_app/app/javascript/components/Top.vue new file mode 100644 index 00000000..0e48f3bf --- /dev/null +++ b/spec/rails_app/app/javascript/components/Top.vue @@ -0,0 +1,101 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/notifications/Index.vue b/spec/rails_app/app/javascript/components/notifications/Index.vue new file mode 100644 index 00000000..2ceafb0b --- /dev/null +++ b/spec/rails_app/app/javascript/components/notifications/Index.vue @@ -0,0 +1,125 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/notifications/Notification.vue b/spec/rails_app/app/javascript/components/notifications/Notification.vue new file mode 100644 index 00000000..55b21ed9 --- /dev/null +++ b/spec/rails_app/app/javascript/components/notifications/Notification.vue @@ -0,0 +1,133 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue b/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue new file mode 100644 index 00000000..9116ef8f --- /dev/null +++ b/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue @@ -0,0 +1,122 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/subscriptions/Index.vue b/spec/rails_app/app/javascript/components/subscriptions/Index.vue new file mode 100644 index 00000000..f33991cb --- /dev/null +++ b/spec/rails_app/app/javascript/components/subscriptions/Index.vue @@ -0,0 +1,279 @@ + + + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue b/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue new file mode 100644 index 00000000..ac72b957 --- /dev/null +++ b/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue @@ -0,0 +1,112 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue b/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue new file mode 100644 index 00000000..6e6199f6 --- /dev/null +++ b/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue @@ -0,0 +1,141 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue b/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue new file mode 100644 index 00000000..8f9c3631 --- /dev/null +++ b/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue @@ -0,0 +1,226 @@ + + + + + \ No newline at end of file diff --git a/spec/rails_app/app/javascript/packs/application.js b/spec/rails_app/app/javascript/packs/application.js new file mode 100644 index 00000000..7c3021d7 --- /dev/null +++ b/spec/rails_app/app/javascript/packs/application.js @@ -0,0 +1,18 @@ +/* eslint no-console:0 */ +// This file is automatically compiled by Webpack, along with any other files +// present in this directory. You're encouraged to place your actual application logic in +// a relevant structure within app/javascript and only use these pack files to reference +// that code so it'll be compiled. +// +// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate +// layout file, like app/views/layouts/application.html.erb + + +// Uncomment to copy all static images under ../images to the output folder and reference +// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) +// or the `imagePath` JavaScript helper below. +// +// const images = require.context('../images', true) +// const imagePath = (name) => images(name, true) + +console.log('Hello World from Webpacker') diff --git a/spec/rails_app/app/javascript/packs/spa.js b/spec/rails_app/app/javascript/packs/spa.js new file mode 100644 index 00000000..b664310c --- /dev/null +++ b/spec/rails_app/app/javascript/packs/spa.js @@ -0,0 +1,11 @@ +import Vue from 'vue' +import axios from 'axios' +import App from '../App.vue' + +axios.defaults.baseURL = "/api/v2" + +document.addEventListener('DOMContentLoaded', () => { + const app = new Vue({ + render: h => h(App) + }).$mount('#spa') +}) diff --git a/spec/rails_app/app/javascript/store/auth.js b/spec/rails_app/app/javascript/store/auth.js new file mode 100644 index 00000000..9fcfaafe --- /dev/null +++ b/spec/rails_app/app/javascript/store/auth.js @@ -0,0 +1,37 @@ +import Vue from 'vue' +import Vuex from 'vuex' +import createPersistedState from "vuex-persistedstate" + +Vue.use(Vuex) + +export default new Vuex.Store({ + state: { + signedInStatus: false, + currentUser: null, + authHeaders: {} + }, + mutations: { + signIn(state, { user, authHeaders }) { + state.currentUser = user; + state.authHeaders = authHeaders; + state.signedInStatus = true; + }, + signOut(state) { + state.signedInStatus = false; + state.currentUser = null; + state.authHeaders = {}; + } + }, + getters: { + userSignedIn(state) { + return state.signedInStatus; + }, + currentUser(state) { + return state.currentUser; + }, + authHeaders(state) { + return state.authHeaders; + } + }, + plugins: [createPersistedState({storage: window.sessionStorage})] +}); \ No newline at end of file diff --git a/spec/rails_app/app/models/user.rb b/spec/rails_app/app/models/user.rb index 6638e104..521152c6 100644 --- a/spec/rails_app/app/models/user.rb +++ b/spec/rails_app/app/models/user.rb @@ -19,6 +19,12 @@ module UserModel def admin? admin.present? end + + def as_json(options = {}) + options[:include] = (options[:include] || {}).merge(admin: { methods: [:printable_target_name] }) + options[:methods] = (options[:methods] || []).push(:printable_target_name) + super(options) + end end unless ENV['AN_TEST_DB'] == 'mongodb' diff --git a/spec/rails_app/app/views/spa/index.html.erb b/spec/rails_app/app/views/spa/index.html.erb new file mode 100644 index 00000000..38f1af7b --- /dev/null +++ b/spec/rails_app/app/views/spa/index.html.erb @@ -0,0 +1,2 @@ +
+<%= javascript_pack_tag 'spa' %> \ No newline at end of file diff --git a/spec/rails_app/babel.config.js b/spec/rails_app/babel.config.js new file mode 100644 index 00000000..12f98da5 --- /dev/null +++ b/spec/rails_app/babel.config.js @@ -0,0 +1,72 @@ +module.exports = function(api) { + var validEnv = ['development', 'test', 'production'] + var currentEnv = api.env() + var isDevelopmentEnv = api.env('development') + var isProductionEnv = api.env('production') + var isTestEnv = api.env('test') + + if (!validEnv.includes(currentEnv)) { + throw new Error( + 'Please specify a valid `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: ' + + JSON.stringify(currentEnv) + + '.' + ) + } + + return { + presets: [ + isTestEnv && [ + '@babel/preset-env', + { + targets: { + node: 'current' + } + } + ], + (isProductionEnv || isDevelopmentEnv) && [ + '@babel/preset-env', + { + forceAllTransforms: true, + useBuiltIns: 'entry', + corejs: 3, + modules: false, + exclude: ['transform-typeof-symbol'] + } + ] + ].filter(Boolean), + plugins: [ + 'babel-plugin-macros', + '@babel/plugin-syntax-dynamic-import', + isTestEnv && 'babel-plugin-dynamic-import-node', + '@babel/plugin-transform-destructuring', + [ + '@babel/plugin-proposal-class-properties', + { + loose: true + } + ], + [ + '@babel/plugin-proposal-object-rest-spread', + { + useBuiltIns: true + } + ], + [ + '@babel/plugin-transform-runtime', + { + helpers: false, + regenerator: true, + corejs: false + } + ], + [ + '@babel/plugin-transform-regenerator', + { + async: false + } + ] + ].filter(Boolean) + } +} diff --git a/spec/rails_app/bin/webpack b/spec/rails_app/bin/webpack new file mode 100755 index 00000000..1031168d --- /dev/null +++ b/spec/rails_app/bin/webpack @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/webpack_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::WebpackRunner.run(ARGV) +end diff --git a/spec/rails_app/bin/webpack-dev-server b/spec/rails_app/bin/webpack-dev-server new file mode 100755 index 00000000..dd966273 --- /dev/null +++ b/spec/rails_app/bin/webpack-dev-server @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= "development" + +require "pathname" +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require "bundler/setup" + +require "webpacker" +require "webpacker/dev_server_runner" + +APP_ROOT = File.expand_path("..", __dir__) +Dir.chdir(APP_ROOT) do + Webpacker::DevServerRunner.run(ARGV) +end diff --git a/spec/rails_app/config/initializers/devise_token_auth.rb b/spec/rails_app/config/initializers/devise_token_auth.rb index 72b13c1b..caff2272 100644 --- a/spec/rails_app/config/initializers/devise_token_auth.rb +++ b/spec/rails_app/config/initializers/devise_token_auth.rb @@ -5,11 +5,11 @@ # client is responsible for keeping track of the changing tokens. Change # this to false to prevent the Authorization header from changing after # each request. - # config.change_headers_on_each_request = true + config.change_headers_on_each_request = false # By default, users will need to re-authenticate after 2 weeks. This setting # determines how long tokens will remain valid after they are issued. - # config.token_lifespan = 2.weeks + config.token_lifespan = 1.hour # Limiting the token_cost to just 4 in testing will increase the performance of # your test suite dramatically. The possible cost value is within range from 4 diff --git a/spec/rails_app/config/routes.rb b/spec/rails_app/config/routes.rb index 6fa3ec19..dc301891 100644 --- a/spec/rails_app/config/routes.rb +++ b/spec/rails_app/config/routes.rb @@ -1,36 +1,46 @@ Rails.application.routes.draw do + # Routes for example Rails application root to: 'articles#index' devise_for :users resources :articles, except: [:destroy] resources :comments, only: [:create, :destroy] + # activity_notification routes for users notify_to :users, with_subscription: true notify_to :users, with_devise: :users, devise_default_routes: true, with_subscription: true + # activity_notification routes for admins + notify_to :admins, with_devise: :users, with_subscription: true + scope :admins, as: :admins do + notify_to :admins, with_devise: :users, devise_default_routes: true, with_subscription: true, routing_scope: :admins + end + + # Routes for single page application working with activity_notification REST API backend + resources :spa, only: [:index] namespace :api do scope :"v#{ActivityNotification::GEM_VERSION::MAJOR}" do mount_devise_token_auth_for 'User', at: 'auth' end end + + # Routes of activity_notification REST API backend for users scope :api do scope :"v#{ActivityNotification::GEM_VERSION::MAJOR}" do notify_to :users, api_mode: true, with_subscription: true notify_to :users, api_mode: true, with_devise: :users, devise_default_routes: true, with_subscription: true resources :apidocs, only: [:index], controller: 'activity_notification/apidocs' + resources :users, only: [:index, :show] end end - notify_to :admins, with_devise: :users, with_subscription: true - scope :admins, as: :admins do - notify_to :admins, with_devise: :users, devise_default_routes: true, with_subscription: true, routing_scope: :admins - end - + # Routes of activity_notification REST API backend for admins scope :api do scope :"v#{ActivityNotification::GEM_VERSION::MAJOR}" do notify_to :admins, api_mode: true, with_devise: :users, with_subscription: true scope :admins, as: :admins do notify_to :admins, api_mode: true, with_devise: :users, devise_default_routes: true, with_subscription: true end + resources :admins, only: [:index, :show] end end end diff --git a/spec/rails_app/config/webpack/development.js b/spec/rails_app/config/webpack/development.js new file mode 100644 index 00000000..c5edff94 --- /dev/null +++ b/spec/rails_app/config/webpack/development.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/spec/rails_app/config/webpack/environment.js b/spec/rails_app/config/webpack/environment.js new file mode 100644 index 00000000..6ef014b0 --- /dev/null +++ b/spec/rails_app/config/webpack/environment.js @@ -0,0 +1,7 @@ +const { environment } = require('@rails/webpacker') +const { VueLoaderPlugin } = require('vue-loader') +const vue = require('./loaders/vue') + +environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin()) +environment.loaders.prepend('vue', vue) +module.exports = environment diff --git a/spec/rails_app/config/webpack/loaders/vue.js b/spec/rails_app/config/webpack/loaders/vue.js new file mode 100644 index 00000000..509c742b --- /dev/null +++ b/spec/rails_app/config/webpack/loaders/vue.js @@ -0,0 +1,6 @@ +module.exports = { + test: /\.vue(\.erb)?$/, + use: [{ + loader: 'vue-loader' + }] +} diff --git a/spec/rails_app/config/webpack/production.js b/spec/rails_app/config/webpack/production.js new file mode 100644 index 00000000..be0f53aa --- /dev/null +++ b/spec/rails_app/config/webpack/production.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'production' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/spec/rails_app/config/webpack/test.js b/spec/rails_app/config/webpack/test.js new file mode 100644 index 00000000..c5edff94 --- /dev/null +++ b/spec/rails_app/config/webpack/test.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/spec/rails_app/config/webpacker.yml b/spec/rails_app/config/webpacker.yml new file mode 100644 index 00000000..79217f35 --- /dev/null +++ b/spec/rails_app/config/webpacker.yml @@ -0,0 +1,97 @@ +# Note: You must restart bin/webpack-dev-server for changes to take effect + +default: &default + source_path: app/javascript + source_entry_path: packs + public_root_path: public + public_output_path: packs + cache_path: tmp/cache/webpacker + check_yarn_integrity: false + webpack_compile_output: true + + # Additional paths webpack should lookup modules + # ['app/assets', 'engine/foo/app/assets'] + resolved_paths: [] + + # Reload manifest.json on all requests so we reload latest compiled packs + cache_manifest: false + + # Extract and emit a css file + extract_css: false + + static_assets_extensions: + - .jpg + - .jpeg + - .png + - .gif + - .tiff + - .ico + - .svg + - .eot + - .otf + - .ttf + - .woff + - .woff2 + + extensions: + - .vue + - .mjs + - .js + - .sass + - .scss + - .css + - .module.sass + - .module.scss + - .module.css + - .png + - .svg + - .gif + - .jpeg + - .jpg + +development: + <<: *default + compile: true + + # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules + check_yarn_integrity: true + + # Reference: https://webpack.js.org/configuration/dev-server/ + dev_server: + https: false + host: localhost + port: 3035 + public: localhost:3035 + hmr: false + # Inline should be set to true if using HMR + inline: true + overlay: true + compress: true + disable_host_check: true + use_local_ip: false + quiet: false + pretty: false + headers: + 'Access-Control-Allow-Origin': '*' + watch_options: + ignored: '**/node_modules/**' + + +test: + <<: *default + compile: true + + # Compile test packs to a separate directory + public_output_path: packs-test + +production: + <<: *default + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Extract and emit a css file + extract_css: false + + # Cache manifest.json for performance + cache_manifest: true diff --git a/spec/rails_app/package.json b/spec/rails_app/package.json new file mode 100644 index 00000000..8f19c223 --- /dev/null +++ b/spec/rails_app/package.json @@ -0,0 +1,21 @@ +{ + "name": "activity_notification", + "description": "Sample single page application for activity_notification using Vue.js", + "dependencies": { + "@rails/webpacker": "^4.2.0", + "axios": "^0.19.0", + "vue": "^2.6.10", + "vuex": "^3.1.2", + "vuex-persistedstate": "^2.7.0", + "vue-loader": "^15.7.2", + "vue-router": "^3.1.3", + "vue-template-compiler": "^2.6.10", + "vue-moment": "^4.1.0", + "vue-moment-tz": "^2.1.1", + "vue-pluralize": "^0.0.2" + }, + "devDependencies": { + "webpack-dev-server": "^3.9.0" + }, + "license": "MIT" +} diff --git a/spec/rails_app/postcss.config.js b/spec/rails_app/postcss.config.js new file mode 100644 index 00000000..aa5998a8 --- /dev/null +++ b/spec/rails_app/postcss.config.js @@ -0,0 +1,12 @@ +module.exports = { + plugins: [ + require('postcss-import'), + require('postcss-flexbugs-fixes'), + require('postcss-preset-env')({ + autoprefixer: { + flexbox: 'no-2009' + }, + stage: 3 + }) + ] +}