├── .gitignore
├── client
├── .babelrc
├── .editorconfig
├── .gitignore
├── README.md
├── build
│ ├── build.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── index.html
├── package.json
├── src
│ ├── assets
│ │ └── milligram.min.css
│ ├── components
│ │ ├── App.vue
│ │ ├── InfiniteScroll.vue
│ │ ├── Login.vue
│ │ ├── Navigation.vue
│ │ ├── NotFound.vue
│ │ ├── Post.vue
│ │ ├── Posts.vue
│ │ ├── SampleData.vue
│ │ └── Signup.vue
│ ├── filters
│ │ └── index.js
│ ├── main.js
│ ├── router
│ │ └── index.js
│ └── services
│ │ └── index.js
└── static
│ ├── .gitkeep
│ ├── login.png
│ └── posts.png
├── readme.md
└── server
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .npmignore
├── LICENSE
├── README.md
├── config
├── default.json
└── production.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── app.js
├── hooks
│ └── index.js
├── index.js
├── middleware
│ ├── index.js
│ ├── logger.js
│ └── not-found-handler.js
├── services
│ ├── authentication
│ │ └── index.js
│ ├── index.js
│ ├── posts
│ │ ├── hooks
│ │ │ ├── author.js
│ │ │ ├── createdAt.js
│ │ │ ├── index.js
│ │ │ └── updatedAt.js
│ │ └── index.js
│ └── user
│ │ ├── hooks
│ │ ├── addRoles.js
│ │ └── index.js
│ │ └── index.js
└── test
│ └── services
│ └── posts
│ └── hooks
│ ├── author.test.js
│ ├── createdAt.test.js
│ └── updatedAt.test.js
└── test
├── app.test.js
└── services
├── posts
├── hooks
│ ├── author.test.js
│ ├── createdAt.test.js
│ └── updatedAt.test.js
└── index.test.js
└── user
├── hooks
└── addRoles.test.js
└── index.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Webstorm #
2 | ############
3 | .idea
4 |
5 | # Logs #
6 | #########
7 | *.log
8 |
9 | # OS generated files #
10 | ######################
11 | .DS_Store
12 | .DS_Store?
13 | ._*
14 | .Spotlight-V100
15 | .Trashes
--------------------------------------------------------------------------------
/client/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-2"],
3 | "plugins": ["transform-runtime"],
4 | "comments": false
5 | }
6 |
--------------------------------------------------------------------------------
/client/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # client
2 |
3 | > A Vue.js project
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 | ```
17 |
18 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
19 |
--------------------------------------------------------------------------------
/client/build/build.js:
--------------------------------------------------------------------------------
1 | // https://github.com/shelljs/shelljs
2 | require('shelljs/global')
3 | env.NODE_ENV = 'production'
4 |
5 | var path = require('path')
6 | var config = require('../config')
7 | var ora = require('ora')
8 | var webpack = require('webpack')
9 | var webpackConfig = require('./webpack.prod.conf')
10 |
11 | console.log(
12 | ' Tip:\n' +
13 | ' Built files are meant to be served over an HTTP server.\n' +
14 | ' Opening index.html over file:// won\'t work.\n'
15 | )
16 |
17 | var spinner = ora('building for production...')
18 | spinner.start()
19 |
20 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
21 | rm('-rf', assetsPath)
22 | mkdir('-p', assetsPath)
23 | cp('-R', 'static/*', assetsPath)
24 |
25 | webpack(webpackConfig, function (err, stats) {
26 | spinner.stop()
27 | if (err) throw err
28 | process.stdout.write(stats.toString({
29 | colors: true,
30 | modules: false,
31 | children: false,
32 | chunks: false,
33 | chunkModules: false
34 | }) + '\n')
35 | })
36 |
--------------------------------------------------------------------------------
/client/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/client/build/dev-server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var express = require('express')
3 | var webpack = require('webpack')
4 | var config = require('../config')
5 | var opn = require('opn')
6 | var proxyMiddleware = require('http-proxy-middleware')
7 | var webpackConfig = require('./webpack.dev.conf')
8 |
9 | // default port where dev server listens for incoming traffic
10 | var port = process.env.PORT || config.dev.port
11 | // Define HTTP proxies to your custom API backend
12 | // https://github.com/chimurai/http-proxy-middleware
13 | var proxyTable = config.dev.proxyTable
14 |
15 | var app = express()
16 | var compiler = webpack(webpackConfig)
17 |
18 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
19 | publicPath: webpackConfig.output.publicPath,
20 | stats: {
21 | colors: true,
22 | chunks: false
23 | }
24 | })
25 |
26 | var hotMiddleware = require('webpack-hot-middleware')(compiler)
27 | // force page reload when html-webpack-plugin template changes
28 | compiler.plugin('compilation', function (compilation) {
29 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
30 | hotMiddleware.publish({ action: 'reload' })
31 | cb()
32 | })
33 | })
34 |
35 | // proxy api requests
36 | Object.keys(proxyTable).forEach(function (context) {
37 | var options = proxyTable[context]
38 | if (typeof options === 'string') {
39 | options = { target: options }
40 | }
41 | app.use(proxyMiddleware(context, options))
42 | })
43 |
44 | // handle fallback for HTML5 history API
45 | app.use(require('connect-history-api-fallback')())
46 |
47 | // serve webpack bundle output
48 | app.use(devMiddleware)
49 |
50 | // enable hot-reload and state-preserving
51 | // compilation error display
52 | app.use(hotMiddleware)
53 |
54 | // serve pure static assets
55 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
56 | app.use(staticPath, express.static('./static'))
57 |
58 | module.exports = app.listen(port, function (err) {
59 | if (err) {
60 | console.log(err)
61 | return
62 | }
63 | var uri = 'http://localhost:' + port
64 | console.log('Listening at ' + uri + '\n')
65 | opn(uri)
66 | })
67 |
--------------------------------------------------------------------------------
/client/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
7 | ? config.build.assetsSubDirectory
8 | : config.dev.assetsSubDirectory
9 | return path.posix.join(assetsSubDirectory, _path)
10 | }
11 |
12 | exports.cssLoaders = function (options) {
13 | options = options || {}
14 | // generate loader string to be used with extract text plugin
15 | function generateLoaders (loaders) {
16 | var sourceLoader = loaders.map(function (loader) {
17 | var extraParamChar
18 | if (/\?/.test(loader)) {
19 | loader = loader.replace(/\?/, '-loader?')
20 | extraParamChar = '&'
21 | } else {
22 | loader = loader + '-loader'
23 | extraParamChar = '?'
24 | }
25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
26 | }).join('!')
27 |
28 | if (options.extract) {
29 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
30 | } else {
31 | return ['vue-style-loader', sourceLoader].join('!')
32 | }
33 | }
34 |
35 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html
36 | return {
37 | css: generateLoaders(['css']),
38 | postcss: generateLoaders(['css']),
39 | less: generateLoaders(['css', 'less']),
40 | sass: generateLoaders(['css', 'sass?indentedSyntax']),
41 | scss: generateLoaders(['css', 'sass']),
42 | stylus: generateLoaders(['css', 'stylus']),
43 | styl: generateLoaders(['css', 'stylus'])
44 | }
45 | }
46 |
47 | // Generate loaders for standalone style files (outside of .vue)
48 | exports.styleLoaders = function (options) {
49 | var output = []
50 | var loaders = exports.cssLoaders(options)
51 | for (var extension in loaders) {
52 | var loader = loaders[extension]
53 | output.push({
54 | test: new RegExp('\\.' + extension + '$'),
55 | loader: loader
56 | })
57 | }
58 | return output
59 | }
60 |
--------------------------------------------------------------------------------
/client/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var utils = require('./utils')
4 | var projectRoot = path.resolve(__dirname, '../')
5 |
6 | module.exports = {
7 | entry: {
8 | app: './src/main.js'
9 | },
10 | output: {
11 | path: config.build.assetsRoot,
12 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
13 | filename: '[name].js'
14 | },
15 | resolve: {
16 | extensions: ['', '.js', '.vue'],
17 | fallback: [path.join(__dirname, '../node_modules')],
18 | alias: {
19 | 'vue': 'vue/dist/vue',
20 | 'src': path.resolve(__dirname, '../src'),
21 | 'assets': path.resolve(__dirname, '../src/assets'),
22 | 'components': path.resolve(__dirname, '../src/components')
23 | }
24 | },
25 | resolveLoader: {
26 | fallback: [path.join(__dirname, '../node_modules')]
27 | },
28 | module: {
29 | loaders: [
30 | {
31 | test: /\.vue$/,
32 | loader: 'vue'
33 | },
34 | {
35 | test: /\.js$/,
36 | loader: 'babel',
37 | include: projectRoot,
38 | exclude: /node_modules/
39 | },
40 | {
41 | test: /\.json$/,
42 | loader: 'json'
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46 | loader: 'url',
47 | query: {
48 | limit: 10000,
49 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
50 | }
51 | },
52 | {
53 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
54 | loader: 'url',
55 | query: {
56 | limit: 10000,
57 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
58 | }
59 | }
60 | ]
61 | },
62 | vue: {
63 | loaders: utils.cssLoaders(),
64 | postcss: [
65 | require('autoprefixer')({
66 | browsers: ['last 2 versions']
67 | })
68 | ]
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/client/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var config = require('../config')
2 | var webpack = require('webpack')
3 | var merge = require('webpack-merge')
4 | var utils = require('./utils')
5 | var baseWebpackConfig = require('./webpack.base.conf')
6 | var HtmlWebpackPlugin = require('html-webpack-plugin')
7 |
8 | // add hot-reload related code to entry chunks
9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
11 | })
12 |
13 | module.exports = merge(baseWebpackConfig, {
14 | module: {
15 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
16 | },
17 | // eval-source-map is faster for development
18 | devtool: '#eval-source-map',
19 | plugins: [
20 | new webpack.DefinePlugin({
21 | 'process.env': config.dev.env
22 | }),
23 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
24 | new webpack.optimize.OccurenceOrderPlugin(),
25 | new webpack.HotModuleReplacementPlugin(),
26 | new webpack.NoErrorsPlugin(),
27 | // https://github.com/ampedandwired/html-webpack-plugin
28 | new HtmlWebpackPlugin({
29 | filename: 'index.html',
30 | template: 'index.html',
31 | inject: true
32 | })
33 | ]
34 | })
35 |
--------------------------------------------------------------------------------
/client/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | var utils = require('./utils')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
8 | var HtmlWebpackPlugin = require('html-webpack-plugin')
9 | var env = config.build.env
10 |
11 | var webpackConfig = merge(baseWebpackConfig, {
12 | module: {
13 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
14 | },
15 | devtool: config.build.productionSourceMap ? '#source-map' : false,
16 | output: {
17 | path: config.build.assetsRoot,
18 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
19 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
20 | },
21 | vue: {
22 | loaders: utils.cssLoaders({
23 | sourceMap: config.build.productionSourceMap,
24 | extract: true
25 | })
26 | },
27 | plugins: [
28 | // http://vuejs.github.io/vue-loader/workflow/production.html
29 | new webpack.DefinePlugin({
30 | 'process.env': env
31 | }),
32 | new webpack.optimize.UglifyJsPlugin({
33 | compress: {
34 | warnings: false
35 | }
36 | }),
37 | new webpack.optimize.OccurenceOrderPlugin(),
38 | // extract css into its own file
39 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
40 | // generate dist index.html with correct asset hash for caching.
41 | // you can customize output by editing /index.html
42 | // see https://github.com/ampedandwired/html-webpack-plugin
43 | new HtmlWebpackPlugin({
44 | filename: config.build.index,
45 | template: 'index.html',
46 | inject: true,
47 | minify: {
48 | removeComments: true,
49 | collapseWhitespace: true,
50 | removeAttributeQuotes: true
51 | // more options:
52 | // https://github.com/kangax/html-minifier#options-quick-reference
53 | },
54 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
55 | chunksSortMode: 'dependency'
56 | }),
57 | // split vendor js into its own file
58 | new webpack.optimize.CommonsChunkPlugin({
59 | name: 'vendor',
60 | minChunks: function (module, count) {
61 | // any required modules inside node_modules are extracted to vendor
62 | return (
63 | module.resource &&
64 | /\.js$/.test(module.resource) &&
65 | module.resource.indexOf(
66 | path.join(__dirname, '../node_modules')
67 | ) === 0
68 | )
69 | }
70 | }),
71 | // extract webpack runtime and module manifest to its own file in order to
72 | // prevent vendor hash from being updated whenever app bundle is updated
73 | new webpack.optimize.CommonsChunkPlugin({
74 | name: 'manifest',
75 | chunks: ['vendor']
76 | })
77 | ]
78 | })
79 |
80 | if (config.build.productionGzip) {
81 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
82 |
83 | webpackConfig.plugins.push(
84 | new CompressionWebpackPlugin({
85 | asset: '[path].gz[query]',
86 | algorithm: 'gzip',
87 | test: new RegExp(
88 | '\\.(' +
89 | config.build.productionGzipExtensions.join('|') +
90 | ')$'
91 | ),
92 | threshold: 10240,
93 | minRatio: 0.8
94 | })
95 | )
96 | }
97 |
98 | module.exports = webpackConfig
99 |
--------------------------------------------------------------------------------
/client/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/client/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 |
4 | module.exports = {
5 | build: {
6 | env: require('./prod.env'),
7 | index: path.resolve(__dirname, '../dist/index.html'),
8 | assetsRoot: path.resolve(__dirname, '../dist'),
9 | assetsSubDirectory: 'static',
10 | assetsPublicPath: '/',
11 | productionSourceMap: true,
12 | // Gzip off by default as many popular static hosts such as
13 | // Surge or Netlify already gzip all static assets for you.
14 | // Before setting to `true`, make sure to:
15 | // npm install --save-dev compression-webpack-plugin
16 | productionGzip: false,
17 | productionGzipExtensions: ['js', 'css']
18 | },
19 | dev: {
20 | env: require('./dev.env'),
21 | port: 8080,
22 | assetsSubDirectory: 'static',
23 | assetsPublicPath: '/',
24 | proxyTable: {},
25 | // CSS Sourcemaps off by default because relative paths are "buggy"
26 | // with this option, according to the CSS-Loader README
27 | // (https://github.com/webpack/css-loader#sourcemaps)
28 | // In our experience, they generally work as expected,
29 | // just be aware of this issue when enabling this option.
30 | cssSourceMap: false
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/client/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Blog Admin
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "delay ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "build": "node build/build.js"
10 | },
11 | "dependencies": {
12 | "feathers-authentication": "^0.7.11",
13 | "feathers-localstorage": "^0.5.0",
14 | "lorem-ipsum": "^1.0.3",
15 | "vue": "^2.0.1",
16 | "vue-infinite-loading": "^2.0.0-rc.1",
17 | "vue-router": "^2.0.0"
18 | },
19 | "devDependencies": {
20 | "autoprefixer": "^6.4.0",
21 | "babel-core": "^6.0.0",
22 | "babel-loader": "^6.0.0",
23 | "babel-plugin-transform-runtime": "^6.0.0",
24 | "babel-preset-es2015": "^6.0.0",
25 | "babel-preset-stage-2": "^6.0.0",
26 | "babel-register": "^6.0.0",
27 | "connect-history-api-fallback": "^1.1.0",
28 | "css-loader": "^0.25.0",
29 | "eventsource-polyfill": "^0.9.6",
30 | "express": "^4.13.3",
31 | "extract-text-webpack-plugin": "^1.0.1",
32 | "feathers": "^2.0.0",
33 | "feathers-socketio": "^1.3.3",
34 | "file-loader": "^0.9.0",
35 | "function-bind": "^1.0.2",
36 | "html-webpack-plugin": "^2.8.1",
37 | "http-proxy-middleware": "^0.17.2",
38 | "json-loader": "^0.5.4",
39 | "opn": "^4.0.2",
40 | "ora": "^0.3.0",
41 | "shelljs": "^0.7.4",
42 | "socket.io-client": "^1.4.5",
43 | "url-loader": "^0.5.7",
44 | "vue-loader": "^9.4.0",
45 | "vue-style-loader": "^1.0.0",
46 | "webpack": "^1.13.2",
47 | "webpack-dev-middleware": "^1.8.3",
48 | "webpack-hot-middleware": "^2.12.2",
49 | "webpack-merge": "^0.14.1"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/client/src/assets/milligram.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Milligram v1.1.0
3 | * http://milligram.github.io
4 | *
5 | * Copyright (c) 2016 CJ Patoilo
6 | * Licensed under the MIT license
7 | */
8 |
9 | html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}*,*:after,*:before{box-sizing:inherit}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{-ms-grid-row-align:flex-start;align-items:flex-start}.row.row-bottom{-ms-grid-row-align:flex-end;align-items:flex-end}.row.row-center{-ms-grid-row-align:center;align-items:center}.row.row-stretch{-ms-grid-row-align:stretch;align-items:stretch}.row.row-baseline{-ms-grid-row-align:baseline;align-items:baseline}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row .column{display:block;flex:1;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:hover,a:focus{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.0rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:3.0rem;line-height:1.3}h4{font-size:2.4rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}@media (min-width: 40rem){h1{font-size:5.0rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3.0rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}
10 |
11 | /*# sourceMappingURL=milligram.min.css.map */
--------------------------------------------------------------------------------
/client/src/components/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | You are logged {{ loggedIn ? 'in' : 'out' }}
11 |
12 |
13 |
14 |
15 |
16 |
27 |
28 |
35 |
36 |
--------------------------------------------------------------------------------
/client/src/components/InfiniteScroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
--------------------------------------------------------------------------------
/client/src/components/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Login
5 |
6 | You need to login first.
7 |
8 |
9 |
Incorrect email or password
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
50 |
51 |
56 |
--------------------------------------------------------------------------------
/client/src/components/Navigation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Blog Admin
4 |
5 |
6 |
7 | Log out
8 |
9 |
10 | Login
11 |
12 |
13 | Signup
14 |
15 |
16 |
17 | Posts
18 |
19 |
20 |
21 | Add Post
22 |
23 |
24 | Create Random Post
25 |
26 |
27 |
28 |
29 |
32 |
43 |
--------------------------------------------------------------------------------
/client/src/components/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
404 Page Not Found
4 |
5 |
6 |
9 |
20 |
--------------------------------------------------------------------------------
/client/src/components/Post.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{heading}}
5 |
6 |
7 |
8 |
9 |
Remove Post
10 |
11 |
12 |
13 |
90 |
97 |
--------------------------------------------------------------------------------
/client/src/components/Posts.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Posts
5 |
6 |
7 | {{post.title}} - {{ post.createdAt | inTimeAgo }}
8 |
9 |
10 |
11 |
12 |
13 |
92 |
93 |
94 |
96 |
--------------------------------------------------------------------------------
/client/src/components/SampleData.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{this.count}}. {{this.title}}
7 |
8 |
9 | {{this.description}}
10 |
11 |
12 |
13 |
44 |
--------------------------------------------------------------------------------
/client/src/components/Signup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Signup
6 | {{successMessage}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
40 |
--------------------------------------------------------------------------------
/client/src/filters/index.js:
--------------------------------------------------------------------------------
1 | export function host(url) {
2 | const host = url.replace(/^https?:\/\//, '').replace(/\/.*$/, '')
3 | const parts = host.split('.').slice(-3)
4 | if (parts[0] === 'www') parts.shift()
5 | return parts.join('.')
6 | }
7 |
8 | /*
9 | export function loggedIn() {
10 | return !!localStorage.getItem("feathers-jwt");
11 | // return !!services.app.get('token'); //currently a bug with this.
12 | }*/
13 |
14 | export function inTimeAgo(time) {
15 | const timeDifference = (Date.now() - time) / 1000;
16 | //handle times in the future
17 | if (timeDifference < 0) {
18 | if (timeDifference < -31104000) {
19 | return pluralize(~~(timeDifference / 31104000), ' year')
20 | } else if (timeDifference < -2592000) {
21 | return pluralize(~~(timeDifference / 2592000), ' month')
22 | } else if (timeDifference < -86400) {
23 | return pluralize(~~(timeDifference / 86400), ' day')
24 | } else if (timeDifference < -3600) {
25 | return pluralize(~~(timeDifference / 3600), ' hour')
26 | } else if (timeDifference < -60) {
27 | return pluralize(~~(timeDifference / 60), ' minute')
28 | } else {
29 | return pluralize(~~(timeDifference), ' second')
30 | }
31 |
32 | } else {
33 | //handle times in the past
34 | if (timeDifference < 60) {
35 | return pluralize(~~(timeDifference), ' second')
36 | } else if (timeDifference < 3600) {
37 | return pluralize(~~(timeDifference / 60), ' minute')
38 | } else if (timeDifference < 86400) {
39 | return pluralize(~~(timeDifference / 3600), ' hour')
40 | } else if (timeDifference < 2592000) {
41 | return pluralize(~~(timeDifference / 86400), ' day')
42 | } else if (timeDifference < 31104000) {
43 | return pluralize(~~(timeDifference / 2592000), ' month')
44 | } else {
45 | return pluralize(~~(timeDifference / 31104000), ' year')
46 | }
47 | }
48 | }
49 |
50 | function pluralize(time, label) {
51 | //handle labels in the future
52 | if (time < 0) {
53 | if (time === -1) {
54 | return 'in ' + -(time) + label
55 | }
56 | return 'in ' + -(time) + label + 's'
57 |
58 | } else {
59 | //handle labels in the past
60 | if (time === 1) {
61 | return time + label + ' ago'
62 | }
63 | return time + label + 's ago'
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './components/App.vue'
3 | import router from './router'
4 | import * as filters from './filters'
5 |
6 | // register global utility filters.
7 | Object.keys(filters).forEach(key => {
8 | Vue.filter(key, filters[key])
9 | });
10 |
11 | const app = new Vue({
12 | router,
13 | ...App // Object spread copying everything from App.vue
14 | }).$mount('#app');
15 |
--------------------------------------------------------------------------------
/client/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | import * as services from '../services'
7 | import Posts from '../components/Posts.vue'
8 | import Signup from '../components/Signup.vue'
9 | import Login from '../components/Login.vue'
10 | import NotFound from '../components/NotFound.vue'
11 | import Post from '../components/Post.vue'
12 | import SampleData from '../components/SampleData.vue'
13 |
14 | function requireAuth (to, from, next) {
15 | //console.log('via feathers method: '+services.app.get('token'));
16 | //console.log('via localstorage method: '+localStorage.getItem("feathers-jwt"));
17 | if (localStorage.getItem("feathers-jwt") === null) { //app.get('token') has a bug in the current build. https://github.com/feathersjs/feathers-authentication/issues/303
18 | // if (services.app.get('token') === null) {
19 | next({
20 | path: '/login',
21 | query: { redirect: to.fullPath }
22 | })
23 | } else {
24 | next()
25 | }
26 | }
27 |
28 |
29 | export default new Router({
30 | mode: 'history',
31 | scrollBehavior: () => ({ y: 0 }),
32 | routes: [
33 | { path: '/', component: Posts, beforeEnter: requireAuth },
34 | { path: '/posts', component: Posts, beforeEnter: requireAuth },
35 | { path: '/login', component: Login },
36 | { path: '/post', component: Post, beforeEnter: requireAuth },
37 | { path: '/signup', component: Signup },
38 | { path: '/post/:id', component: Post},
39 | { path: '/logout',
40 | beforeEnter (to, from, next) {
41 | services.app.logout();
42 | next('/login')
43 | }
44 | },
45 | { path: '/sampledata', component: SampleData },
46 | { path: '*', component: NotFound }
47 | ]
48 | })
49 |
--------------------------------------------------------------------------------
/client/src/services/index.js:
--------------------------------------------------------------------------------
1 | // /services/index.js
2 | import io from 'socket.io-client';
3 | import feathers from 'feathers/client';
4 | import hooks from 'feathers-hooks';
5 | import socketio from 'feathers-socketio/client';
6 | import localstorage from 'feathers-localstorage';
7 | import authentication from 'feathers-authentication/client';
8 |
9 | const socket = io('http://localhost:3030');
10 |
11 | export const app = feathers()
12 | .configure(socketio(socket)) // you could use Primus or REST instead
13 | .configure(hooks())
14 | .configure(authentication({ storage: window.localStorage }));
15 |
16 | socket.on('reconnect', () => {app.authenticate();}); //https://github.com/feathersjs/feathers-authentication/issues/272#issuecomment-240937322
17 |
18 | // repeat this line for every service in our backend
19 | export const postService = app.service('posts');
20 | export const userService = app.service('users');
21 |
--------------------------------------------------------------------------------
/client/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delay/feathers-vue-blog-admin-demo/ad01313cb2a544db4382a17fc45a4db3afdf5733/client/static/.gitkeep
--------------------------------------------------------------------------------
/client/static/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delay/feathers-vue-blog-admin-demo/ad01313cb2a544db4382a17fc45a4db3afdf5733/client/static/login.png
--------------------------------------------------------------------------------
/client/static/posts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delay/feathers-vue-blog-admin-demo/ad01313cb2a544db4382a17fc45a4db3afdf5733/client/static/posts.png
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Feathers and Vue 2.0 Blog Admin Demo
2 |
3 | This demo app shows how to use Feathers.js with Vue 2.0. It includes
4 | using authentication, vue-router and roles. It also uses
5 | vue-infinite-loading to load the posts as the user scrolls.
6 | It uses Feathers on the server side.
7 |
8 | 
9 |
10 | 
11 |
12 |
13 | ```
14 | In the client directory
15 |
16 | bash
17 | # install dependencies
18 | npm install
19 |
20 | # serve in dev mode, with hot reload at localhost:8080
21 | npm run dev
22 |
23 | Web browser: http://localhost:8080/
24 |
25 | Create a user and you can generate some sample posts. Make sure you
26 | also install and run feathers server side component as well so data can
27 | be added and saved.
28 |
29 | ```
30 |
31 | ```
32 | In the server directory. This installs and runs the feathers server side services.
33 |
34 | bash
35 | # install dependencies
36 | npm install
37 |
38 | # serve in dev mode, with hot reload at localhost:8080
39 | npm start
40 |
41 | Web browser: http://localhost:3030/
42 |
43 | ```
44 |
45 | I made this to learn some of the basics on using these two awesome
46 | frameworks together. I looked at several helpful tutorials and
47 | documentation including the official docs on [Feathers](https://docs.feathersjs.com/) and [Vue](http://vuejs.org/guide/).
48 | These three examples were also very helpful.
49 |
50 | * [Vue Hackernews Clone 2.0](https://github.com/vuejs/vue-hackernews-2.0)
51 | * [Realtime Vue.js and Feathers.js Example](https://www.youtube.com/watch?v=zbhYcxr5ldk)
52 | * [Vue Router Examples](https://github.com/vuejs/vue-router/tree/dev/examples)
53 |
54 | I am still learning so if you have any suggestions about how to make the code better, please
55 | let me know.
56 |
57 | Thanks so much to everyone who contributed to these two great frameworks!
--------------------------------------------------------------------------------
/server/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
30 | lib/
31 | data/
32 |
--------------------------------------------------------------------------------
/server/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "eqeqeq": true,
8 | "immed": true,
9 | "indent": 2,
10 | "latedef": "nofunc",
11 | "newcap": false,
12 | "noarg": true,
13 | "quotmark": "single",
14 | "regexp": true,
15 | "undef": true,
16 | "unused": false,
17 | "strict": false,
18 | "trailing": true,
19 | "smarttabs": true,
20 | "white": false,
21 | "globals": {
22 | "it": true,
23 | "describe": true,
24 | "before": true,
25 | "beforeEach": true,
26 | "after": true,
27 | "afterEach": true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
30 | data/
31 |
--------------------------------------------------------------------------------
/server/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Feathers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # server
2 |
3 | >
4 |
5 | ## About
6 |
7 | This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications.
8 |
9 | ## Getting Started
10 |
11 | Getting up and running is as easy as 1, 2, 3.
12 |
13 | 1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed.
14 | 2. Install your dependencies
15 |
16 | ```
17 | cd path/to/server; npm install
18 | ```
19 |
20 | 3. Start your app
21 |
22 | ```
23 | npm start
24 | ```
25 |
26 | ## Testing
27 |
28 | Simply run `npm test` and all your tests in the `test/` directory will be run.
29 |
30 | ## Scaffolding
31 |
32 | Feathers has a powerful command line interface. Here are a few things it can do:
33 |
34 | ```
35 | $ npm install -g feathers-cli # Install Feathers CLI
36 |
37 | $ feathers generate service # Generate a new Service
38 | $ feathers generate hook # Generate a new Hook
39 | $ feathers generate model # Generate a new Model
40 | $ feathers help # Show all commands
41 | ```
42 |
43 | ## Help
44 |
45 | For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com).
46 |
47 | ## Changelog
48 |
49 | __0.1.0__
50 |
51 | - Initial release
52 |
53 | ## License
54 |
55 | Copyright (c) 2016
56 |
57 | Licensed under the [MIT license](LICENSE).
58 |
--------------------------------------------------------------------------------
/server/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "localhost",
3 | "port": 3030,
4 | "nedb": "../data/",
5 | "public": "../public/",
6 | "auth": {
7 | "token": {
8 | "secret": "V2A9RLTVETguNTxuDz+8Ahv+CJncrML3L/X2HEktak6D8pJrrXAmb8Hdzvecy72ss2M2A80dn09VvOAWzDmp7w=="
9 | },
10 | "local": {}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/server/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 | "host": "server-app.feathersjs.com",
3 | "port": 80,
4 | "nedb": "NEDB_BASE_PATH",
5 | "public": "../public/",
6 | "auth": {
7 | "token": {
8 | "secret": "FEATHERS_AUTH_SECRET"
9 | },
10 | "local": {}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "description": "",
4 | "version": "0.0.0",
5 | "homepage": "",
6 | "main": "src/",
7 | "keywords": [
8 | "feathers"
9 | ],
10 | "license": "MIT",
11 | "repository": {},
12 | "author": {},
13 | "contributors": [],
14 | "bugs": {},
15 | "engines": {
16 | "node": ">= 0.12.0"
17 | },
18 | "scripts": {
19 | "test": "npm run jshint && npm run mocha",
20 | "jshint": "jshint src/. test/. --config",
21 | "start": "node src/",
22 | "mocha": "mocha test/ --recursive"
23 | },
24 | "dependencies": {
25 | "body-parser": "^1.15.2",
26 | "compression": "^1.6.2",
27 | "cors": "^2.8.1",
28 | "feathers": "^2.0.2",
29 | "feathers-authentication": "^0.7.11",
30 | "feathers-configuration": "^0.3.3",
31 | "feathers-errors": "^2.4.0",
32 | "feathers-hooks": "^1.5.8",
33 | "feathers-nedb": "^2.5.1",
34 | "feathers-rest": "^1.5.0",
35 | "feathers-socketio": "^1.4.1",
36 | "nedb": "^1.8.0",
37 | "passport": "^0.3.2",
38 | "serve-favicon": "^2.3.0",
39 | "winston": "^2.2.0"
40 | },
41 | "devDependencies": {
42 | "jshint": "^2.9.3",
43 | "mocha": "^3.1.0",
44 | "request": "^2.75.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/server/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/delay/feathers-vue-blog-admin-demo/ad01313cb2a544db4382a17fc45a4db3afdf5733/server/public/favicon.ico
--------------------------------------------------------------------------------
/server/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Welcome to Feathers
4 |
62 |
63 |
64 |
65 |
66 | A minimalist real-time framework for tomorrow's apps.
67 |
68 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/server/src/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const serveStatic = require('feathers').static;
5 | const favicon = require('serve-favicon');
6 | const compress = require('compression');
7 | const cors = require('cors');
8 | const feathers = require('feathers');
9 | const configuration = require('feathers-configuration');
10 | const hooks = require('feathers-hooks');
11 | const rest = require('feathers-rest');
12 | const bodyParser = require('body-parser');
13 | const socketio = require('feathers-socketio');
14 | const middleware = require('./middleware');
15 | const services = require('./services');
16 |
17 | const app = feathers();
18 |
19 | app.configure(configuration(path.join(__dirname, '..')));
20 |
21 | app.use(compress())
22 | .options('*', cors())
23 | .use(cors())
24 | .use(favicon( path.join(app.get('public'), 'favicon.ico') ))
25 | .use('/', serveStatic( app.get('public') ))
26 | .use(bodyParser.json())
27 | .use(bodyParser.urlencoded({ extended: true }))
28 | .configure(hooks())
29 | .configure(rest())
30 | .configure(socketio())
31 | .configure(services)
32 | .configure(middleware);
33 |
34 | module.exports = app;
35 |
--------------------------------------------------------------------------------
/server/src/hooks/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Add any common hooks you want to share across services in here.
4 | //
5 | // Below is an example of how a hook is written and exported. Please
6 | // see http://docs.feathersjs.com/hooks/readme.html for more details
7 | // on hooks.
8 |
9 | exports.myHook = function(options) {
10 | return function(hook) {
11 | console.log('My custom global hook ran. Feathers is awesome!');
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/server/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const app = require('./app');
4 | const port = app.get('port');
5 | const server = app.listen(port);
6 |
7 | server.on('listening', () =>
8 | console.log(`Feathers application started on ${app.get('host')}:${port}`)
9 | );
--------------------------------------------------------------------------------
/server/src/middleware/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const handler = require('feathers-errors/handler');
4 | const notFound = require('./not-found-handler');
5 | const logger = require('./logger');
6 |
7 | module.exports = function() {
8 | // Add your custom middleware here. Remember, that
9 | // just like Express the order matters, so error
10 | // handling middleware should go last.
11 | const app = this;
12 |
13 | app.use(notFound());
14 | app.use(logger(app));
15 | app.use(handler());
16 | };
17 |
--------------------------------------------------------------------------------
/server/src/middleware/logger.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const winston = require('winston');
4 |
5 | module.exports = function(app) {
6 | // Add a logger to our app object for convenience
7 | app.logger = winston;
8 |
9 | return function(error, req, res, next) {
10 | if (error) {
11 | const message = `${error.code ? `(${error.code}) ` : '' }Route: ${req.url} - ${error.message}`;
12 |
13 | if (error.code === 404) {
14 | winston.info(message);
15 | }
16 | else {
17 | winston.error(message);
18 | winston.info(error.stack);
19 | }
20 | }
21 |
22 | next(error);
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/server/src/middleware/not-found-handler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const errors = require('feathers-errors');
4 |
5 | module.exports = function() {
6 | return function(req, res, next) {
7 | next(new errors.NotFound('Page not found'));
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/server/src/services/authentication/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const authentication = require('feathers-authentication');
4 |
5 |
6 | module.exports = function() {
7 | const app = this;
8 |
9 | let config = app.get('auth');
10 |
11 |
12 |
13 | app.configure(authentication(config));
14 | };
15 |
--------------------------------------------------------------------------------
/server/src/services/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const posts = require('./posts');
3 | const authentication = require('./authentication');
4 | const user = require('./user');
5 |
6 | module.exports = function() {
7 | const app = this;
8 |
9 |
10 | app.configure(authentication);
11 | app.configure(user);
12 | app.configure(posts);
13 | };
14 |
--------------------------------------------------------------------------------
/server/src/services/posts/hooks/author.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // src/services/posts/hooks/author.js
4 | //
5 | // Use this hook to manipulate incoming or outgoing data.
6 | // For more information on hooks see: http://docs.feathersjs.com/hooks/readme.html
7 |
8 | const defaults = {};
9 |
10 | module.exports = function(options) {
11 | options = Object.assign({}, defaults, options);
12 |
13 | return function(hook) {
14 | const user = hook.params.user;
15 | //if (hook.data.author == '') {
16 | const author = user.firstName + ' ' + user.lastName;
17 | hook.data.author = author;
18 | //}
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/server/src/services/posts/hooks/createdAt.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // src/services/posts/hooks/createdAt.js
4 | //
5 | // Use this hook to manipulate incoming or outgoing data.
6 | // For more information on hooks see: http://docs.feathersjs.com/hooks/readme.html
7 |
8 | const defaults = {};
9 |
10 | module.exports = function(options) {
11 | options = Object.assign({}, defaults, options);
12 |
13 | return function(hook) {
14 | hook.data.createdAt = new Date().getTime();
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/server/src/services/posts/hooks/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const author = require('./author');
4 |
5 | const updatedAt = require('./updatedAt');
6 |
7 | const createdAt = require('./createdAt');
8 |
9 | const globalHooks = require('../../../hooks');
10 | const hooks = require('feathers-hooks');
11 | const auth = require('feathers-authentication').hooks;
12 |
13 |
14 | exports.before = {
15 | all: [
16 | auth.verifyToken(),
17 | auth.populateUser(),
18 | auth.restrictToAuthenticated()
19 | ],
20 | find: [],
21 | get: [],
22 | create: [auth.restrictToRoles({roles: ['admin', 'editor']/*,
23 | fieldName: 'permissions',
24 | idField: 'id',
25 | ownerField: 'sentBy',
26 | owner: true*/
27 | }),
28 | createdAt(), updatedAt(), author()],
29 | update: [updatedAt(auth.restrictToRoles({roles: ['admin', 'editor']}))],
30 | patch: [updatedAt()],
31 | remove: [auth.restrictToRoles({roles: ['admin', 'editor']})]
32 | };
33 |
34 | exports.after = {
35 | all: [],
36 | find: [],
37 | get: [],
38 | create: [],
39 | update: [],
40 | patch: [],
41 | remove: []
42 | };
43 |
--------------------------------------------------------------------------------
/server/src/services/posts/hooks/updatedAt.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // src/services/posts/hooks/updatedAt.js
4 | //
5 | // Use this hook to manipulate incoming or outgoing data.
6 | // For more information on hooks see: http://docs.feathersjs.com/hooks/readme.html
7 |
8 | const defaults = {};
9 |
10 | module.exports = function(options) {
11 | options = Object.assign({}, defaults, options);
12 |
13 | return function(hook) {
14 | hook.data.updatedAt = new Date().getTime();
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/server/src/services/posts/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const NeDB = require('nedb');
5 | const service = require('feathers-nedb');
6 | const hooks = require('./hooks');
7 |
8 | module.exports = function(){
9 | const app = this;
10 |
11 | const db = new NeDB({
12 | filename: path.join(app.get('nedb'), 'posts.db'),
13 | autoload: true
14 | });
15 |
16 | let options = {
17 | Model: db,
18 | paginate: {
19 | default: 30,
20 | max: 100
21 | }
22 | };
23 |
24 | // Initialize our service with any options it requires
25 | app.use('/posts', service(options));
26 |
27 | // Get our initialize service to that we can bind hooks
28 | const postsService = app.service('/posts');
29 |
30 | // Set up our before hooks
31 | postsService.before(hooks.before);
32 |
33 | // Set up our after hooks
34 | postsService.after(hooks.after);
35 | };
36 |
--------------------------------------------------------------------------------
/server/src/services/user/hooks/addRoles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // src/services/user/hooks/addRoles.js
4 | //
5 | // Use this hook to manipulate incoming or outgoing data.
6 | // For more information on hooks see: http://docs.feathersjs.com/hooks/readme.html
7 |
8 | const defaults = {};
9 |
10 | module.exports = function(options) {
11 | options = Object.assign({}, defaults, options);
12 |
13 | return function(hook) {
14 | hook.addRoles = true;
15 | const roles = ['editor','admin']
16 | hook.data.roles = roles;
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/server/src/services/user/hooks/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const addRoles = require('./addRoles');
4 |
5 | const globalHooks = require('../../../hooks');
6 | const hooks = require('feathers-hooks');
7 | const auth = require('feathers-authentication').hooks;
8 |
9 | exports.before = {
10 | all: [],
11 | find: [
12 | auth.verifyToken(),
13 | auth.populateUser(),
14 | auth.restrictToAuthenticated()
15 | ],
16 | get: [
17 | auth.verifyToken(),
18 | auth.populateUser(),
19 | auth.restrictToAuthenticated(),
20 | auth.restrictToOwner({ ownerField: '_id' })
21 | ],
22 | create: [auth.hashPassword(), addRoles()],
23 | update: [
24 | auth.verifyToken(),
25 | auth.populateUser(),
26 | auth.restrictToAuthenticated(),
27 | auth.restrictToOwner({ ownerField: '_id' })
28 | ],
29 | patch: [
30 | auth.verifyToken(),
31 | auth.populateUser(),
32 | auth.restrictToAuthenticated(),
33 | auth.restrictToOwner({ ownerField: '_id' })
34 | ],
35 | remove: [
36 | auth.verifyToken(),
37 | auth.populateUser(),
38 | auth.restrictToAuthenticated(),
39 | auth.restrictToOwner({ ownerField: '_id' })
40 | ]
41 | };
42 |
43 | exports.after = {
44 | all: [hooks.remove('password')],
45 | find: [],
46 | get: [],
47 | create: [],
48 | update: [],
49 | patch: [],
50 | remove: []
51 | };
52 |
--------------------------------------------------------------------------------
/server/src/services/user/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const NeDB = require('nedb');
5 | const service = require('feathers-nedb');
6 | const hooks = require('./hooks');
7 |
8 | module.exports = function(){
9 | const app = this;
10 |
11 | const db = new NeDB({
12 | filename: path.join(app.get('nedb'), 'users.db'),
13 | autoload: true
14 | });
15 |
16 | let options = {
17 | Model: db,
18 | paginate: {
19 | default: 5,
20 | max: 25
21 | }
22 | };
23 |
24 | // Initialize our service with any options it requires
25 | app.use('/users', service(options));
26 |
27 | // Get our initialize service to that we can bind hooks
28 | const userService = app.service('/users');
29 |
30 | // Set up our before hooks
31 | userService.before(hooks.before);
32 |
33 | // Set up our after hooks
34 | userService.after(hooks.after);
35 | };
36 |
--------------------------------------------------------------------------------
/server/src/test/services/posts/hooks/author.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const author = require('../../../../src/services/posts/hooks/author.js');
5 |
6 | describe('posts author hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | author()(mockHook);
17 |
18 | assert.ok(mockHook.author);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/src/test/services/posts/hooks/createdAt.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const createdAt = require('../../../../src/services/posts/hooks/createdAt.js');
5 |
6 | describe('posts createdAt hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | createdAt()(mockHook);
17 |
18 | assert.ok(mockHook.createdAt);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/src/test/services/posts/hooks/updatedAt.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const updatedAt = require('../../../../src/services/posts/hooks/updatedAt.js');
5 |
6 | describe('posts updatedAt hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | updatedAt()(mockHook);
17 |
18 | assert.ok(mockHook.updatedAt);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/test/app.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const request = require('request');
5 | const app = require('../src/app');
6 |
7 | describe('Feathers application tests', function() {
8 | before(function(done) {
9 | this.server = app.listen(3030);
10 | this.server.once('listening', () => done());
11 | });
12 |
13 | after(function(done) {
14 | this.server.close(done);
15 | });
16 |
17 | it('starts and shows the index page', function(done) {
18 | request('http://localhost:3030', function(err, res, body) {
19 | assert.ok(body.indexOf('') !== -1);
20 | done(err);
21 | });
22 | });
23 |
24 | describe('404', function() {
25 | it('shows a 404 HTML page', function(done) {
26 | request({
27 | url: 'http://localhost:3030/path/to/nowhere',
28 | headers: {
29 | 'Accept': 'text/html'
30 | }
31 | }, function(err, res, body) {
32 | assert.equal(res.statusCode, 404);
33 | assert.ok(body.indexOf('') !== -1);
34 | done(err);
35 | });
36 | });
37 |
38 | it('shows a 404 JSON error without stack trace', function(done) {
39 | request({
40 | url: 'http://localhost:3030/path/to/nowhere',
41 | json: true
42 | }, function(err, res, body) {
43 | assert.equal(res.statusCode, 404);
44 | assert.equal(body.code, 404);
45 | assert.equal(body.message, 'Page not found');
46 | assert.equal(body.name, 'NotFound');
47 | done(err);
48 | });
49 | });
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/server/test/services/posts/hooks/author.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const author = require('../../../../src/services/posts/hooks/author.js');
5 |
6 | describe('posts author hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | author()(mockHook);
17 |
18 | assert.ok(mockHook.author);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/test/services/posts/hooks/createdAt.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const createdAt = require('../../../../src/services/posts/hooks/createdAt.js');
5 |
6 | describe('posts createdAt hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | createdAt()(mockHook);
17 |
18 | assert.ok(mockHook.createdAt);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/test/services/posts/hooks/updatedAt.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const updatedAt = require('../../../../src/services/posts/hooks/updatedAt.js');
5 |
6 | describe('posts updatedAt hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | updatedAt()(mockHook);
17 |
18 | assert.ok(mockHook.updatedAt);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/test/services/posts/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const app = require('../../../src/app');
5 |
6 | describe('posts service', function() {
7 | it('registered the posts service', () => {
8 | assert.ok(app.service('posts'));
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/server/test/services/user/hooks/addRoles.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const addRoles = require('../../../../src/services/user/hooks/addRoles.js');
5 |
6 | describe('user addRoles hook', function() {
7 | it('hook can be used', function() {
8 | const mockHook = {
9 | type: 'before',
10 | app: {},
11 | params: {},
12 | result: {},
13 | data: {}
14 | };
15 |
16 | addRoles()(mockHook);
17 |
18 | assert.ok(mockHook.addRoles);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/server/test/services/user/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const app = require('../../../src/app');
5 |
6 | describe('user service', function() {
7 | it('registered the users service', () => {
8 | assert.ok(app.service('users'));
9 | });
10 | });
11 |
--------------------------------------------------------------------------------