├── .babelrc ├── .editorconfig ├── .gitignore ├── Procfile ├── README.md ├── build ├── build.js ├── check-versions.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 ├── favicon.ico ├── index.html ├── package.json ├── server.js ├── src ├── App.vue ├── db.js ├── directives.js ├── example-config.js ├── filters.js ├── main.js ├── mixins.js ├── pages │ └── Index.vue ├── store │ └── index.js └── styles │ ├── main.scss │ └── variables.scss └── static ├── .gitkeep └── favicon.ico /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false, 5 | "env": { 6 | "test": { 7 | "plugins": [ "istanbul" ] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | gae-server/vendor 4 | dist/ 5 | npm-debug.log 6 | src/config.js 7 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node server.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Google API + Firebase - Boilerplate 2 | 3 | ## Setup Configuration 4 | copy the `example-config.js` and save it as `config.js` in the same location. 5 | 6 | ### Firebase Config 7 | From overview , click add Firebase to your web app and copy those value under `firebase: {...}` 8 | 9 | ### Google API Config 10 | from https://console.developers.google.com you need to enable a few APIs. Click Enable API, from the list add Google Calendar API and Google+ API. You can add anything you want, just make sure to add it to the `discovery_docs` array if you would like to access with in your app. 11 | 12 | Under Credentials, click Create credentials -> API Key. add this to `google` in the config.js file. If there is not already a OAuth 2.0 client IDs create one from the Create credentials drop down. Click the pencil and copy over the Client ID to the config.js file. 13 | 14 | You can add a `hosted_domain` making it so that only people from a given domain that use google can app your app. ie: biz.com. 15 | 16 | You need to add the scope for any of the APIs that you added, in this example we are using google+ and the calendar. You can see more scopes here: https://developers.google.com/identity/protocols/googlescopes 17 | 18 | We need to add the `discovery_docs` that we need people and calendar. To add more view here: https://developers.google.com/discovery/v1/getting_started 19 | 20 | ```javascrip 21 | module.exports = { 22 | google: { 23 | client_id: '****.apps.googleusercontent.com', 24 | api_key: '****', 25 | hosted_domain: '****.com', 26 | scope: 'profile email https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/plus.login', 27 | discovery_docs: [ 28 | 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest', 29 | 'https://people.googleapis.com/$discovery/rest?version=v1' 30 | ], 31 | }, 32 | firebase: { 33 | apiKey: '****', 34 | authDomain: '****.firebaseapp.com', 35 | databaseURL: 'https://****.firebaseio.com', 36 | storageBucket: '****.appspot.com', 37 | messagingSenderId: '****' 38 | } 39 | } 40 | ``` 41 | 42 | ## Build Setup 43 | 44 | ``` bash 45 | # install dependencies 46 | npm install 47 | 48 | # serve with hot reload at localhost:8080 49 | npm run dev 50 | 51 | # build for production with minification 52 | npm run build 53 | ``` 54 | 55 | ## Deploy & Authorization 56 | This app is ready to deploy to heroku just add from github (with a config.js file) or from the heroku toolbelt. We ignore the `config.js` so we need to force add this if we want to keep it out of our Github repo. There are many ways to do this, but here is one solution. 57 | 58 | Create an app in the Heroku dashboard. 59 | 60 | Login to heroku with the heroku toolbelt. 61 | `heroku login` 62 | 63 | Add the remote 64 | `heroku git:remote -a {your-app-name}` 65 | 66 | Authorize domain. In the Google console under the OAuth 2.0 Client IDs you need to add the heroku domain to Authorized JavaScript origins and Authorized redirect URIs, then click save. This may take a few minutes to take affect. 67 | 68 | ``` 69 | # create a deploy branch that we do not track on heroku 70 | git branch deploy 71 | git checkout deploy 72 | 73 | # add everything and force add the config.js file 74 | git add . 75 | git add src/config.js -f 76 | 77 | # commit and push deploy to heroku 78 | git commit -am "Deploy to heroku!" 79 | git push heroku deploy:master 80 | ``` 81 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('./check-versions')() 3 | require('shelljs/global') 4 | env.NODE_ENV = 'production' 5 | 6 | var path = require('path') 7 | var config = require('../config') 8 | var ora = require('ora') 9 | var webpack = require('webpack') 10 | var webpackConfig = require('./webpack.prod.conf') 11 | 12 | console.log( 13 | ' Tip:\n' + 14 | ' Built files are meant to be served over an HTTP server.\n' + 15 | ' Opening index.html over file:// won\'t work.\n' 16 | ) 17 | 18 | var spinner = ora('building for production...') 19 | spinner.start() 20 | 21 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 22 | rm('-rf', assetsPath) 23 | mkdir('-p', assetsPath) 24 | cp('-R', 'static/*', assetsPath) 25 | 26 | webpack(webpackConfig, function (err, stats) { 27 | spinner.stop() 28 | if (err) throw err 29 | process.stdout.write(stats.toString({ 30 | colors: true, 31 | modules: false, 32 | children: false, 33 | chunks: false, 34 | chunkModules: false 35 | }) + '\n') 36 | }) 37 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var semver = require('semver') 2 | var chalk = require('chalk') 3 | var packageConfig = require('../package.json') 4 | var exec = function (cmd) { 5 | return require('child_process') 6 | .execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | { 16 | name: 'npm', 17 | currentVersion: exec('npm --version'), 18 | versionRequirement: packageConfig.engines.npm 19 | } 20 | ] 21 | 22 | module.exports = function () { 23 | var warnings = [] 24 | for (var i = 0; i < versionRequirements.length; i++) { 25 | var mod = versionRequirements[i] 26 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 27 | warnings.push(mod.name + ': ' + 28 | chalk.red(mod.currentVersion) + ' should be ' + 29 | chalk.green(mod.versionRequirement) 30 | ) 31 | } 32 | } 33 | 34 | if (warnings.length) { 35 | console.log('') 36 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 37 | console.log() 38 | for (var i = 0; i < warnings.length; i++) { 39 | var warning = warnings[i] 40 | console.log(' ' + warning) 41 | } 42 | console.log() 43 | process.exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | var config = require('../config') 3 | if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 4 | var path = require('path') 5 | var express = require('express') 6 | var webpack = require('webpack') 7 | var opn = require('opn') 8 | var proxyMiddleware = require('http-proxy-middleware') 9 | var webpackConfig = require('./webpack.dev.conf') 10 | 11 | // default port where dev server listens for incoming traffic 12 | var port = process.env.PORT || config.dev.port 13 | // Define HTTP proxies to your custom API backend 14 | // https://github.com/chimurai/http-proxy-middleware 15 | var proxyTable = config.dev.proxyTable 16 | 17 | var app = express() 18 | var compiler = webpack(webpackConfig) 19 | 20 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 21 | publicPath: webpackConfig.output.publicPath, 22 | quiet: true 23 | }) 24 | 25 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 26 | log: () => {} 27 | }) 28 | // force page reload when html-webpack-plugin template changes 29 | compiler.plugin('compilation', function (compilation) { 30 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 31 | hotMiddleware.publish({ action: 'reload' }) 32 | cb() 33 | }) 34 | }) 35 | 36 | // proxy api requests 37 | Object.keys(proxyTable).forEach(function (context) { 38 | var options = proxyTable[context] 39 | if (typeof options === 'string') { 40 | options = { target: options } 41 | } 42 | app.use(proxyMiddleware(context, options)) 43 | }) 44 | 45 | // handle fallback for HTML5 history API 46 | app.use(require('connect-history-api-fallback')()) 47 | 48 | // serve webpack bundle output 49 | app.use(devMiddleware) 50 | 51 | // enable hot-reload and state-preserving 52 | // compilation error display 53 | app.use(hotMiddleware) 54 | 55 | // serve pure static assets 56 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 57 | app.use(staticPath, express.static('./static')) 58 | 59 | var uri = 'http://localhost:' + port 60 | 61 | devMiddleware.waitUntilValid(function () { 62 | console.log('> Listening at ' + uri + '\n') 63 | }) 64 | 65 | module.exports = app.listen(port, function (err) { 66 | if (err) { 67 | console.log(err) 68 | return 69 | } 70 | 71 | // when env is testing, don't need open it 72 | if (process.env.NODE_ENV !== 'testing') { 73 | opn(uri) 74 | } 75 | }) 76 | -------------------------------------------------------------------------------- /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 | // Extract CSS when that option is specified 29 | // (which is the case during production build) 30 | if (options.extract) { 31 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 32 | } else { 33 | return ['vue-style-loader', sourceLoader].join('!') 34 | } 35 | } 36 | 37 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html 38 | return { 39 | css: generateLoaders(['css']), 40 | postcss: generateLoaders(['css']), 41 | less: generateLoaders(['css', 'less']), 42 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 43 | scss: generateLoaders(['css', 'sass']), 44 | stylus: generateLoaders(['css', 'stylus']), 45 | styl: generateLoaders(['css', 'stylus']) 46 | } 47 | } 48 | 49 | // Generate loaders for standalone style files (outside of .vue) 50 | exports.styleLoaders = function (options) { 51 | var output = [] 52 | var loaders = exports.cssLoaders(options) 53 | for (var extension in loaders) { 54 | var loader = loaders[extension] 55 | output.push({ 56 | test: new RegExp('\\.' + extension + '$'), 57 | loader: loader 58 | }) 59 | } 60 | return output 61 | } 62 | -------------------------------------------------------------------------------- /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 | var env = process.env.NODE_ENV 7 | // check env & config/index.js to decide whether to enable CSS source maps for the 8 | // various preprocessor loaders added to vue-loader at the end of this file 9 | var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) 10 | var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) 11 | var useCssSourceMap = cssSourceMapDev || cssSourceMapProd 12 | 13 | module.exports = { 14 | entry: { 15 | app: './src/main.js' 16 | }, 17 | output: { 18 | path: config.build.assetsRoot, 19 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, 20 | filename: '[name].js' 21 | }, 22 | resolve: { 23 | extensions: ['', '.js', '.vue', '.json'], 24 | fallback: [path.join(__dirname, '../node_modules')], 25 | alias: { 26 | 'vue$': 'vue/dist/vue.common.js', 27 | 'src': path.resolve(__dirname, '../src'), 28 | 'assets': path.resolve(__dirname, '../src/assets'), 29 | 'components': path.resolve(__dirname, '../src/components') 30 | } 31 | }, 32 | resolveLoader: { 33 | fallback: [path.join(__dirname, '../node_modules')] 34 | }, 35 | module: { 36 | loaders: [ 37 | { 38 | test: /\.vue$/, 39 | loader: 'vue' 40 | }, 41 | { 42 | test: /\.js$/, 43 | loader: 'babel', 44 | include: [ 45 | path.join(projectRoot, 'src') 46 | ], 47 | exclude: /node_modules/ 48 | }, 49 | { 50 | test: /\.json$/, 51 | loader: 'json' 52 | }, 53 | { 54 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 55 | loader: 'url', 56 | query: { 57 | limit: 10000, 58 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 59 | } 60 | }, 61 | { 62 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 63 | loader: 'url', 64 | query: { 65 | limit: 10000, 66 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 67 | } 68 | } 69 | ] 70 | }, 71 | vue: { 72 | loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), 73 | postcss: [ 74 | require('autoprefixer')({ 75 | browsers: ['last 2 versions'] 76 | }) 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /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 | var FriendlyErrors = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // eval-source-map is faster for development 19 | devtool: '#eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.optimize.OccurrenceOrderPlugin(), 26 | new webpack.HotModuleReplacementPlugin(), 27 | new webpack.NoErrorsPlugin(), 28 | // https://github.com/ampedandwired/html-webpack-plugin 29 | new HtmlWebpackPlugin({ 30 | filename: 'index.html', 31 | template: 'index.html', 32 | inject: true 33 | }), 34 | new FriendlyErrors() 35 | ] 36 | }) 37 | -------------------------------------------------------------------------------- /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/en/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.OccurrenceOrderPlugin(), 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanderlin/google-api-firebase-boilerplate/7135788d6c16d0834d138840442099737883d443/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Google API + Firebase 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "80faces", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "kernism", 6 | "private": true, 7 | "scripts": { 8 | "dev": "NODE_ENV=production node build/dev-server.js", 9 | "build": "node build/build.js", 10 | "lint": "eslint --ext .js,.vue src", 11 | "start": "node server", 12 | "prod-test": "NODE_ENV=production node server", 13 | "postinstall": "npm run build" 14 | }, 15 | "dependencies": { 16 | "animate-sass": "^0.6.6", 17 | "animate.css": "^3.5.2", 18 | "autoprefixer": "^6.4.0", 19 | "babel-core": "^6.0.0", 20 | "babel-loader": "^6.0.0", 21 | "babel-plugin-transform-runtime": "^6.0.0", 22 | "babel-preset-es2015": "^6.0.0", 23 | "babel-preset-stage-2": "^6.0.0", 24 | "babel-register": "^6.0.0", 25 | "bulma": "^0.3.1", 26 | "chalk": "^1.1.3", 27 | "connect-history-api-fallback": "^1.3.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 | "file-loader": "^0.9.0", 33 | "firebase": "^3.6.7", 34 | "font-awesome": "^4.7.0", 35 | "friendly-errors-webpack-plugin": "^1.1.2", 36 | "function-bind": "^1.0.2", 37 | "html-webpack-plugin": "^2.8.1", 38 | "http-proxy-middleware": "^0.17.2", 39 | "json-loader": "^0.5.4", 40 | "moment": "^2.17.1", 41 | "node-sass": "^4.3.0", 42 | "opn": "^4.0.2", 43 | "ora": "^0.3.0", 44 | "sass-easing": "^1.0.3", 45 | "sass-loader": "^4.1.1", 46 | "semver": "^5.3.0", 47 | "shelljs": "^0.7.4", 48 | "url-loader": "^0.5.7", 49 | "vue": "^2.1.0", 50 | "vue-loader": "^10.0.0", 51 | "vue-router": "^2.1.3", 52 | "vue-style-loader": "^1.0.0", 53 | "vue-template-compiler": "^2.1.0", 54 | "vuefire": "^1.3.0", 55 | "vuex": "^2.1.1", 56 | "webpack": "^1.13.2", 57 | "webpack-dev-middleware": "^1.8.3", 58 | "webpack-hot-middleware": "^2.12.2", 59 | "webpack-merge": "^0.14.1" 60 | }, 61 | "engines": { 62 | "node": ">= 4.0.0", 63 | "npm": ">= 3.0.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'); 2 | var express = require('express'); 3 | var path = require('path'); 4 | var serveStatic = require('serve-static'); 5 | var history = require('connect-history-api-fallback'); 6 | 7 | if (!process.env.NODE_ENV) process.env.NODE_ENV = config.dev.env.NODE_ENV 8 | 9 | // create the express app 10 | var app = express(); 11 | app.use(history()); 12 | 13 | app.all('*', function(req, res, next) { 14 | var origin = req.get('origin'); 15 | res.header('Access-Control-Allow-Origin', origin); 16 | res.header("Access-Control-Allow-Headers", "X-Requested-With"); 17 | res.header('Access-Control-Allow-Headers', 'Content-Type'); 18 | next(); 19 | }) 20 | 21 | app.use(express.static(path.join(__dirname, 'dist'))); // "public" off of current is root 22 | 23 | app.get('*', function(req,res){ 24 | res.sendFile(path.resolve(__dirname, 'dist/index.html')); 25 | }); 26 | 27 | // kick of the app 28 | var port = process.env.PORT || 5000; 29 | app.listen(port); 30 | 31 | console.log(`Node Env: ${process.env.NODE_ENV}`); 32 | console.log(`server started on port: ${port}`); -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 24 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | var env = process.env.NODE_ENV; 2 | import firebase from 'firebase' 3 | import config from './config' 4 | firebase.initializeApp(config.firebase) 5 | export default { 6 | ref(path) { 7 | return firebase.database().ref(`${config.app_name}/${env}/${path}`) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/directives.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | export default { 3 | } 4 | -------------------------------------------------------------------------------- /src/example-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app_name: 'google-api-firebase', 3 | google: { 4 | client_id: '****.apps.googleusercontent.com', 5 | api_key: '****', 6 | hosted_domain: '****.com', 7 | scope: 'profile email https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/plus.login', 8 | discovery_docs: [ 9 | 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest', 10 | 'https://people.googleapis.com/$discovery/rest?version=v1' 11 | ], 12 | }, 13 | firebase: { 14 | apiKey: '****', 15 | authDomain: '****.firebaseapp.com', 16 | databaseURL: 'https://****.firebaseio.com', 17 | storageBucket: '****.appspot.com', 18 | messagingSenderId: '****' 19 | } 20 | } -------------------------------------------------------------------------------- /src/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export default { 4 | uppercase (str) { 5 | return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; 6 | }, 7 | normalcase (str) { 8 | if (!str) { 9 | return '' 10 | } 11 | var s = str.toLowerCase() 12 | return s.charAt(0).toUpperCase() + s.slice(1) 13 | }, 14 | fromnow (time) { 15 | if (time) { 16 | return moment(time).fromNow() 17 | } else { 18 | return ''; 19 | } 20 | }, 21 | dateFormat (time, format) { 22 | format = format || 'MMM YYYY' 23 | if (time) { 24 | return moment(time).format(format) 25 | } else { 26 | return ''; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router' 2 | import Vuefire from 'vuefire' 3 | import Vue from 'vue' 4 | import App from './App' 5 | import firebase from 'firebase' 6 | import store from './store' 7 | import mixins from './mixins' 8 | import DB from './db' 9 | import filters from './filters' 10 | import directives from './directives' 11 | import config from './config' 12 | 13 | Vue.use(Vuefire) 14 | Vue.use(VueRouter) 15 | 16 | // filters 17 | Object.keys(filters).forEach((name) => { 18 | Vue.filter(name, filters[name]); 19 | }) 20 | 21 | // directive 22 | Object.keys(directives).forEach((name) => { 23 | Vue.directive(name, directives[name]); 24 | }) 25 | 26 | // mixins 27 | Vue.mixin(mixins) 28 | 29 | // these are all our pages 30 | import Index from './pages/Index' 31 | 32 | // authentication guard 33 | const authRequired = (to, from, next) => { 34 | if (!store.state.authenticated) { 35 | next({path: '/login'}) 36 | } else { 37 | next() 38 | } 39 | } 40 | 41 | // vue routes 42 | const routes = [ 43 | { 44 | name: 'home', 45 | path: '/', 46 | component: Index 47 | }, 48 | { 49 | name: 'login', 50 | path: '/login', 51 | component: Index, 52 | beforeEnter: (to, from, next) => { 53 | if (store.state.authenticated) { 54 | next() 55 | } else { 56 | var auth = gapi.auth2.getAuthInstance() 57 | auth.signIn().then((e) => { 58 | console.log('SignIn'); 59 | updateAuthStatus().then(() => { 60 | next({name: 'home'}) 61 | }) 62 | }) 63 | } 64 | } 65 | }, 66 | { 67 | name: 'logout', 68 | path: '/logout', 69 | component: Index, 70 | beforeEnter: (to, from, next) => { 71 | var auth = gapi.auth2.getAuthInstance() 72 | auth.signOut().then(() => { 73 | document.location = '/' 74 | }) 75 | } 76 | }, 77 | { 78 | path: '*', 79 | component: Index, 80 | beforeEnter: (to, from, next) => { 81 | next({path: '/'}) 82 | } 83 | } 84 | ] 85 | 86 | // this is the conductor for which page we are on 87 | const router = new VueRouter({routes: routes}) 88 | 89 | // init the google api 90 | function initGoogleAPI() { 91 | return new Promise((resolve) => { 92 | var script = document.createElement('script'); 93 | script.type = 'text/javascript'; 94 | script.src = 'https://apis.google.com/js/api.js'; 95 | script.onload = (e) => { 96 | gapi.load('client:auth2', () => { 97 | gapi.client.init({ 98 | apiKey: config.google.api_key, 99 | clientId: config.google.client_id, 100 | discoveryDocs: config.google.discovery_docs, 101 | scope: config.google.scope, 102 | }).then(() => { 103 | store.commit('currentUserProfile', null) 104 | store.commit('authenticated', false) 105 | resolve() 106 | }) 107 | }); 108 | } 109 | document.getElementsByTagName('head')[0].appendChild(script); 110 | }) 111 | } 112 | 113 | function updateAuthStatus() { 114 | return new Promise((resolve) => { 115 | 116 | // we have no auth! 117 | if (!gapi.auth2.getAuthInstance()) { 118 | store.commit('authenticated', false) 119 | resolve(false) 120 | return; 121 | } 122 | 123 | var googleUser = gapi.auth2.getAuthInstance().currentUser.get() 124 | var googleProfile = googleUser.getBasicProfile() 125 | var isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get() 126 | 127 | if (isSignedIn) { 128 | 129 | var credential = firebase.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token); 130 | // we now want to auth in to Firebase with the googleUser credentials 131 | // this function is a bit magic, it will create the user as well - if it does not exist 132 | firebase.auth().signInWithCredential(credential).then((user) => { 133 | 134 | // do we have a user? 135 | var hasUser = Boolean(user !== null) 136 | 137 | if (hasUser) { 138 | DB.ref('profiles').child(user.uid).once('value', (snap) => { 139 | var profile = snap.val() 140 | console.log(profile); 141 | if (profile === null) { 142 | var payload = { 143 | email: user.email, 144 | displayName: user.displayName, 145 | photoURL: user.photoURL, 146 | refreshToken: user.refreshToken, 147 | uid: user.uid 148 | } 149 | snap.ref.update(payload).then((e) => { 150 | store.commit('currentUserProfile', payload) 151 | store.commit('authenticated', hasUser) 152 | resolve() 153 | }) 154 | } else { 155 | store.commit('currentUserProfile', profile) 156 | store.commit('authenticated', hasUser) 157 | resolve() 158 | } 159 | }) 160 | } else { 161 | resolve(hasUser) 162 | } 163 | }) 164 | } else { 165 | store.commit('authenticated', false) 166 | resolve(false) 167 | } 168 | }) 169 | } 170 | 171 | function init() { 172 | return new Promise((resolve) => { 173 | // make sure the google api script is loaded 174 | initGoogleAPI().then(() => { 175 | updateAuthStatus().then(() => { 176 | resolve() 177 | }) 178 | }) 179 | }) 180 | } 181 | 182 | // kick off the vue appp 183 | init().then((status) => { 184 | new Vue({ 185 | store, 186 | router, 187 | ...App 188 | }).$mount('#app'); 189 | }) -------------------------------------------------------------------------------- /src/mixins.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash') 2 | var moment = require('moment') 3 | var firebase = require('firebase') 4 | module.exports = { 5 | computed: { 6 | currentUser() { 7 | return firebase.auth().currentUser 8 | }, 9 | currentUserProfile() { 10 | return this.$store.state.currentUserProfile 11 | }, 12 | authenticated() { 13 | return this.$store.state.authenticated 14 | } 15 | }, 16 | methods: { 17 | firebaseTimestamp() { 18 | return firebase.database.ServerValue.TIMESTAMP 19 | }, 20 | signin() { 21 | this.$router.push({name: 'login'}) 22 | }, 23 | signout() { 24 | this.$router.push({name: 'logout'}) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 118 | 119 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | authenticated: false, 9 | currentUserProfile: null, 10 | googleUser: null 11 | }, 12 | 13 | mutations: { 14 | authenticated (state, payload) { 15 | state.authenticated = payload; 16 | }, 17 | googleUser (state, payload) { 18 | state.googleUser = payload; 19 | }, 20 | currentUserProfile (state, payload) { 21 | state.currentUserProfile = payload; 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700|Oswald:400,700'); 3 | @import '~font-awesome/css/font-awesome.css'; 4 | @import '~animate.css/animate.css'; 5 | @font-face { 6 | font-family: 'FontAwesome'; 7 | src: url('~font-awesome/fonts/fontawesome-webfont.eot'); 8 | src: url('~font-awesome/fonts/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), 9 | url('~font-awesome/fonts/fontawesome-webfont.woff') format('woff'), 10 | url('~font-awesome/fonts/fontawesome-webfont.ttf') format('truetype'), 11 | url('~font-awesome/fonts/fontawesome-webfont.svg') format('svg'); 12 | font-weight: bold; 13 | font-style: normal; 14 | } 15 | @import '~bulma'; 16 | 17 | .animated { 18 | animation-duration: 100ms !important; 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanderlin/google-api-firebase-boilerplate/7135788d6c16d0834d138840442099737883d443/src/styles/variables.scss -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanderlin/google-api-firebase-boilerplate/7135788d6c16d0834d138840442099737883d443/static/.gitkeep -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vanderlin/google-api-firebase-boilerplate/7135788d6c16d0834d138840442099737883d443/static/favicon.ico --------------------------------------------------------------------------------