├── client ├── static │ └── .gitkeep ├── .eslintignore ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js ├── .gitignore ├── .editorconfig ├── .postcssrc.js ├── build │ ├── dev-client.js │ ├── vue-loader.conf.js │ ├── build.js │ ├── webpack.dev.conf.js │ ├── check-versions.js │ ├── webpack.base.conf.js │ ├── utils.js │ ├── dev-server.js │ └── webpack.prod.conf.js ├── .babelrc ├── README.md ├── src │ ├── components │ │ ├── Home.vue │ │ ├── UserInfo.vue │ │ ├── SecretQuote.vue │ │ ├── Login.vue │ │ └── Signup.vue │ ├── auth │ │ └── index.js │ ├── main.js │ └── App.vue ├── .eslintrc.js ├── index.html └── package.json ├── Godeps ├── Readme └── Godeps.json ├── .gitignore ├── api ├── quotes.go ├── api.go └── users.go ├── .vscode └── launch.json ├── server.go ├── models ├── db.go ├── users.go └── quotes.go ├── LICENSE ├── routes └── routes.go ├── README.md └── auth └── auth.go /client/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # file types 2 | *.o 3 | *.a 4 | *.so 5 | *.exe 6 | *.test 7 | *.prof 8 | *.db 9 | *.DS_Store 10 | 11 | # folders 12 | vendor 13 | node_modules 14 | tmp 15 | -------------------------------------------------------------------------------- /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/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | -------------------------------------------------------------------------------- /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/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "presets": ["env", "stage-2"], 11 | "plugins": [ "istanbul" ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /api/quotes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "net/http" 4 | 5 | // Quote - 6 | func (api *API) Quote(w http.ResponseWriter, req *http.Request) { 7 | quote := api.quotes.RandomQuote() 8 | w.Write([]byte(quote.Text)) 9 | } 10 | 11 | // SecretQuote - 12 | func (api *API) SecretQuote(w http.ResponseWriter, req *http.Request) { 13 | quote := api.quotes.RandomQuote() 14 | w.Write([]byte(quote.Text)) 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "remotePath": "", 10 | "port": 2345, 11 | "host": "127.0.0.1", 12 | "program": "${fileDirname}", 13 | "env": {}, 14 | "args": [], 15 | "showLog": true 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/markcheno/go-vue-starter/api" 5 | "github.com/markcheno/go-vue-starter/models" 6 | "github.com/markcheno/go-vue-starter/routes" 7 | "github.com/urfave/negroni" 8 | ) 9 | 10 | func main() { 11 | db := models.NewSqliteDB("data.db") 12 | api := api.NewAPI(db) 13 | routes := routes.NewRoutes(api) 14 | n := negroni.Classic() 15 | n.UseHandler(routes) 16 | n.Run(":3000") 17 | } 18 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/markcheno/go-vue-starter/models" 5 | ) 6 | 7 | // API - 8 | type API struct { 9 | users *models.UserManager 10 | quotes *models.QuoteManager 11 | } 12 | 13 | // NewAPI - 14 | func NewAPI(db *models.DB) *API { 15 | 16 | usermgr, _ := models.NewUserManager(db) 17 | quotemgr, _ := models.NewQuoteManager(db) 18 | 19 | return &API{ 20 | users: usermgr, 21 | quotes: quotemgr, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # starter 2 | 3 | > Go/Vue starter 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 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | 20 | # run unit tests 21 | npm run unit 22 | 23 | # run e2e tests 24 | npm run e2e 25 | 26 | # run all tests 27 | npm test 28 | ``` 29 | 30 | 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). 31 | -------------------------------------------------------------------------------- /client/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quote Server 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/components/UserInfo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 37 | -------------------------------------------------------------------------------- /models/db.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | // postgress db driver 6 | _ "github.com/jinzhu/gorm/dialects/postgres" 7 | // sqlite db driver 8 | _ "github.com/jinzhu/gorm/dialects/sqlite" 9 | ) 10 | 11 | // DB abstraction 12 | type DB struct { 13 | *gorm.DB 14 | } 15 | 16 | // NewPostgresDB - postgres database 17 | func NewPostgresDB(dataSourceName string) *DB { 18 | 19 | db, err := gorm.Open("postgres", dataSourceName) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | if err = db.DB().Ping(); err != nil { 25 | panic(err) 26 | } 27 | 28 | //db.LogMode(true) 29 | 30 | return &DB{db} 31 | } 32 | 33 | // NewSqliteDB - sqlite database 34 | func NewSqliteDB(databaseName string) *DB { 35 | 36 | db, err := gorm.Open("sqlite3", databaseName) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | if err = db.DB().Ping(); err != nil { 42 | panic(err) 43 | } 44 | 45 | //db.LogMode(true) 46 | 47 | return &DB{db} 48 | } 49 | -------------------------------------------------------------------------------- /client/src/components/SecretQuote.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | -------------------------------------------------------------------------------- /client/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mark Chenoweth 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 | asdfasdfasdfasdfasdfasdf 24 | -------------------------------------------------------------------------------- /client/src/auth/index.js: -------------------------------------------------------------------------------- 1 | const API_URL = '/api/user/' 2 | const LOGIN_URL = API_URL + 'login' 3 | const SIGNUP_URL = API_URL + 'signup' 4 | 5 | export default { 6 | 7 | login (context, creds, redirect) { 8 | context.$http.post(LOGIN_URL, creds).then(response => { 9 | localStorage.setItem('id_token', response.body.id_token) 10 | if (redirect) { 11 | context.$router.replace(redirect) 12 | } 13 | }, response => { 14 | context.error = response.statusText 15 | }) 16 | }, 17 | 18 | signup (context, creds, redirect) { 19 | context.$http.post(SIGNUP_URL, creds).then(response => { 20 | localStorage.setItem('id_token', response.body.id_token) 21 | if (redirect) { 22 | context.$router.replace(redirect) 23 | } 24 | }, response => { 25 | context.error = response.statusText 26 | }) 27 | }, 28 | 29 | logout (context) { 30 | localStorage.removeItem('id_token') 31 | context.$router.replace('/home') 32 | }, 33 | 34 | isAuthenticated () { 35 | var jwt = localStorage.getItem('id_token') 36 | if (jwt) { 37 | return true 38 | } 39 | return false 40 | }, 41 | 42 | getAuthHeader () { 43 | return { 44 | 'Authorization': 'Bearer ' + localStorage.getItem('id_token') 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | "github.com/markcheno/go-vue-starter/api" 8 | "github.com/markcheno/go-vue-starter/auth" 9 | "github.com/urfave/negroni" 10 | ) 11 | 12 | // NewRoutes builds the routes for the api 13 | func NewRoutes(api *api.API) *mux.Router { 14 | 15 | mux := mux.NewRouter() 16 | 17 | // client static files 18 | mux.Handle("/", http.FileServer(http.Dir("./client/dist/"))).Methods("GET") 19 | mux.PathPrefix("/static/js").Handler(http.StripPrefix("/static/js/", http.FileServer(http.Dir("./client/dist/static/js/")))) 20 | 21 | // api 22 | a := mux.PathPrefix("/api").Subrouter() 23 | 24 | // users 25 | u := a.PathPrefix("/user").Subrouter() 26 | u.HandleFunc("/signup", api.UserSignup).Methods("POST") 27 | u.HandleFunc("/login", api.UserLogin).Methods("POST") 28 | u.Handle("/info", negroni.New( 29 | negroni.HandlerFunc(auth.JwtMiddleware.HandlerWithNext), 30 | negroni.Wrap(http.HandlerFunc(api.UserInfo)), 31 | )) 32 | 33 | // quotes 34 | q := a.PathPrefix("/quote").Subrouter() 35 | q.HandleFunc("/random", api.Quote).Methods("GET") 36 | q.Handle("/protected/random", negroni.New( 37 | negroni.HandlerFunc(auth.JwtMiddleware.HandlerWithNext), 38 | negroni.Wrap(http.HandlerFunc(api.SecretQuote)), 39 | )) 40 | 41 | return mux 42 | } 43 | -------------------------------------------------------------------------------- /client/src/components/Login.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 53 | -------------------------------------------------------------------------------- /client/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = 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 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-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.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-vue-starter 2 | 3 | 4 | 5 | Copyright 2017 Oliver Chang 6 | 7 | ## Golang Starter project with Vue.js single page client 8 | 9 | ### Work in progress... 10 | 11 | ### Features: 12 | 13 | - Middleware: [Negroni](https://github.com/urfave/negroni) 14 | 15 | - Router: [Gorilla](https://github.com/gorilla/mux) 16 | 17 | - Orm: [Gorm](https://github.com/jinzhu/gorm) (sqlite or postgres) 18 | 19 | - Jwt authentication: [jwt-go](https://github.com/dgrijalva/jwt-go) and [go-jwt-middleware](https://github.com/auth0/go-jwt-middleware) 20 | 21 | - [Vue.js](https://vuejs.org/) spa client with webpack 22 | 23 | - User management 24 | 25 | ### TODO: 26 | - config from file 27 | 28 | - email confirmation 29 | 30 | - logrus 31 | 32 | - letsencrypt tls 33 | 34 | ### To get started: 35 | 36 | ``` bash 37 | # clone repository 38 | go get github.com/stack-guru/go-vue-starter 39 | cd $GOPATH/src/github.com/stack-guru/go-vue-starter 40 | 41 | # install Go dependencies (and make sure ports 3000/8080 are open) 42 | go get -u ./... 43 | go run server.go 44 | 45 | # open a new terminal and change to the client dir 46 | cd client 47 | 48 | # install dependencies 49 | npm install 50 | 51 | # serve with hot reload at localhost:8080 52 | npm run dev 53 | 54 | # build for production with minification 55 | npm run build 56 | ``` 57 | 58 | ### License 59 | 60 | MIT License - see LICENSE for more details 61 | -------------------------------------------------------------------------------- /client/src/components/Signup.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 55 | -------------------------------------------------------------------------------- /client/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').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 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | jwtmiddleware "github.com/auth0/go-jwt-middleware" 8 | jwt "github.com/dgrijalva/jwt-go" 9 | "github.com/markcheno/go-vue-starter/models" 10 | ) 11 | 12 | // signingKey set up a global string for our secret 13 | var signingKey = []byte("knrjkevdckjh") 14 | 15 | // JwtMiddleware handler for jwt tokens 16 | var JwtMiddleware = jwtmiddleware.New(jwtmiddleware.Options{ 17 | ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { 18 | return signingKey, nil 19 | }, 20 | UserProperty: "user", 21 | Debug: false, 22 | SigningMethod: jwt.SigningMethodHS256, 23 | }) 24 | 25 | // GetToken create a jwt token with user claims 26 | func GetToken(user *models.User) string { 27 | token := jwt.New(jwt.SigningMethodHS256) 28 | claims := token.Claims.(jwt.MapClaims) 29 | claims["uuid"] = user.UUID 30 | claims["exp"] = time.Now().Add(time.Hour * 24).Unix() 31 | signedToken, _ := token.SignedString(signingKey) 32 | return signedToken 33 | } 34 | 35 | // GetJSONToken create a JSON token string 36 | func GetJSONToken(user *models.User) string { 37 | token := GetToken(user) 38 | jsontoken := "{\"id_token\": \"" + token + "\"}" 39 | return jsontoken 40 | } 41 | 42 | // GetUserClaimsFromContext return "user" claims as a map from request 43 | func GetUserClaimsFromContext(req *http.Request) map[string]interface{} { 44 | //claims := context.Get(req, "user").(*jwt.Token).Claims.(jwt.MapClaims) 45 | claims := req.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims) 46 | return claims 47 | } 48 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import Home from '@/components/Home' 6 | import Login from '@/components/Login' 7 | import Signup from '@/components/Signup' 8 | import SecretQuote from '@/components/SecretQuote' 9 | import UserInfo from '@/components/UserInfo' 10 | 11 | import VueResource from 'vue-resource' 12 | Vue.use(VueResource) 13 | 14 | import VueRouter from 'vue-router' 15 | Vue.use(VueRouter) 16 | 17 | Vue.config.productionTip = true 18 | 19 | import auth from './auth' 20 | 21 | function requireAuth (to, from, next) { 22 | if (!auth.isAuthenticated()) { 23 | this.$router.replace('/login') 24 | } else { 25 | next() 26 | } 27 | } 28 | 29 | const router = new VueRouter({ 30 | mode: 'history', 31 | // base: __dirname, 32 | routes: [ 33 | { 34 | path: '/', 35 | component: Home 36 | }, 37 | { 38 | path: '/home', 39 | name: 'home', 40 | component: Home 41 | }, 42 | { 43 | path: '/login', 44 | name: 'login', 45 | component: Login 46 | }, 47 | { 48 | path: '/signup', 49 | name: 'signup', 50 | component: Signup 51 | }, 52 | { 53 | path: '/secretquote', 54 | name: 'secretquote', 55 | component: SecretQuote, 56 | beforeEnter: requireAuth 57 | }, 58 | { 59 | path: '/userinfo', 60 | name: 'userinfo', 61 | component: UserInfo, 62 | beforeEnter: requireAuth 63 | } 64 | ] 65 | }) 66 | 67 | /* eslint-disable no-new */ 68 | new Vue({ 69 | el: '#app', 70 | router, 71 | template: '', 72 | components: { App } 73 | }) 74 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 54 | -------------------------------------------------------------------------------- /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: false, 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 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | // proxyTable: {}, 31 | proxyTable: { 32 | '/api': { 33 | target: 'http://localhost:3000/api', 34 | changeOrigin: true, 35 | pathRewrite: { 36 | '^/api': '' 37 | } 38 | } 39 | }, 40 | // CSS Sourcemaps off by default because relative paths are "buggy" 41 | // with this option, according to the CSS-Loader README 42 | // (https://github.com/webpack/css-loader#sourcemaps) 43 | // In our experience, they generally work as expected, 44 | // just be aware of this issue when enabling this option. 45 | cssSourceMap: false 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|vue)$/, 32 | loader: 'eslint-loader', 33 | enforce: 'pre', 34 | include: [resolve('src'), resolve('test')], 35 | options: { 36 | formatter: require('eslint-friendly-formatter') 37 | } 38 | }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: vueLoaderConfig 43 | }, 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [resolve('src'), resolve('test')] 48 | }, 49 | { 50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 63 | } 64 | } 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter", 3 | "version": "1.0.0", 4 | "description": "Go/Vue starter project", 5 | "author": "Mark Chenoweth", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src" 12 | }, 13 | "dependencies": { 14 | "hoek": "^5.0.3", 15 | "mime": "^2.3.1", 16 | "npm": "^6.14.6", 17 | "tough-cookie": "^2.4.3", 18 | "vue": "^2.5.16", 19 | "vue-resource": "^1.5.0", 20 | "vue-router": "^2.8.1" 21 | }, 22 | "devDependencies": { 23 | "autoprefixer": "^6.7.2", 24 | "babel-core": "^6.26.2", 25 | "babel-eslint": "^7.2.3", 26 | "babel-loader": "^6.2.10", 27 | "babel-plugin-transform-runtime": "^6.22.0", 28 | "babel-preset-env": "^1.6.1", 29 | "babel-preset-stage-2": "^6.22.0", 30 | "babel-register": "^6.26.0", 31 | "chalk": "^1.1.3", 32 | "chromedriver": "^2.38.2", 33 | "compression-webpack-plugin": "^2.0.0", 34 | "connect-history-api-fallback": "^1.5.0", 35 | "copy-webpack-plugin": "^4.5.1", 36 | "css-loader": "^2.1.1", 37 | "eslint": "^4.19.1", 38 | "eslint-config-standard": "^6.2.1", 39 | "eslint-friendly-formatter": "^2.0.7", 40 | "eslint-loader": "^1.9.0", 41 | "eslint-plugin-html": "^4.0.1", 42 | "eslint-plugin-promise": "^3.7.0", 43 | "eslint-plugin-standard": "^2.0.1", 44 | "eventsource-polyfill": "^0.9.6", 45 | "express": "^4.16.3", 46 | "extract-text-webpack-plugin": "^2.1.2", 47 | "file-loader": "^0.10.0", 48 | "friendly-errors-webpack-plugin": "^1.7.0", 49 | "html-webpack-plugin": "^2.30.1", 50 | "http-proxy-middleware": "^0.19.1", 51 | "opn": "^4.0.2", 52 | "optimize-css-assets-webpack-plugin": "^5.0.1", 53 | "ora": "^1.4.0", 54 | "rimraf": "^2.6.2", 55 | "semver": "^5.5.0", 56 | "shelljs": "^0.7.8", 57 | "url-loader": "^1.1.2", 58 | "vue-loader": "^11.1.4", 59 | "vue-style-loader": "^2.0.0", 60 | "vue-template-compiler": "^2.5.16", 61 | "webpack": "^2.7.0", 62 | "webpack-bundle-analyzer": "^3.3.2", 63 | "webpack-dev-middleware": "^1.12.2", 64 | "webpack-hot-middleware": "^2.22.1", 65 | "webpack-merge": "^2.6.1" 66 | }, 67 | "engines": { 68 | "node": ">= 4.0.0", 69 | "npm": ">= 3.0.0" 70 | }, 71 | "browserslist": [ 72 | "> 1%", 73 | "last 2 versions", 74 | "not ie <= 8" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /api/users.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/markcheno/go-vue-starter/auth" 8 | "github.com/markcheno/go-vue-starter/models" 9 | ) 10 | 11 | // UserJSON - json data expected for login/signup 12 | type UserJSON struct { 13 | Username string `json:"username"` 14 | Password string `json:"password"` 15 | } 16 | 17 | // UserSignup - 18 | func (api *API) UserSignup(w http.ResponseWriter, req *http.Request) { 19 | 20 | decoder := json.NewDecoder(req.Body) 21 | jsondata := UserJSON{} 22 | err := decoder.Decode(&jsondata) 23 | 24 | if err != nil || jsondata.Username == "" || jsondata.Password == "" { 25 | http.Error(w, "Missing username or password", http.StatusBadRequest) 26 | return 27 | } 28 | 29 | if api.users.HasUser(jsondata.Username) { 30 | http.Error(w, "username already exists", http.StatusBadRequest) 31 | return 32 | } 33 | 34 | user := api.users.AddUser(jsondata.Username, jsondata.Password) 35 | 36 | jsontoken := auth.GetJSONToken(user) 37 | 38 | w.Header().Set("Content-Type", "application/json") 39 | w.Write([]byte(jsontoken)) 40 | } 41 | 42 | // UserLogin - 43 | func (api *API) UserLogin(w http.ResponseWriter, req *http.Request) { 44 | 45 | decoder := json.NewDecoder(req.Body) 46 | jsondata := UserJSON{} 47 | err := decoder.Decode(&jsondata) 48 | 49 | if err != nil || jsondata.Username == "" || jsondata.Password == "" { 50 | http.Error(w, "Missing username or password", http.StatusBadRequest) 51 | return 52 | } 53 | 54 | user := api.users.FindUser(jsondata.Username) 55 | if user.Username == "" { 56 | http.Error(w, "username not found", http.StatusBadRequest) 57 | return 58 | } 59 | 60 | if !api.users.CheckPassword(user.Password, jsondata.Password) { 61 | http.Error(w, "bad password", http.StatusBadRequest) 62 | return 63 | } 64 | 65 | jsontoken := auth.GetJSONToken(user) 66 | 67 | w.Header().Set("Content-Type", "application/json") 68 | w.Write([]byte(jsontoken)) 69 | 70 | } 71 | 72 | // GetUserFromContext - return User reference from header token 73 | func (api *API) GetUserFromContext(req *http.Request) *models.User { 74 | userclaims := auth.GetUserClaimsFromContext(req) 75 | user := api.users.FindUserByUUID(userclaims["uuid"].(string)) 76 | return user 77 | } 78 | 79 | // UserInfo - example to get 80 | func (api *API) UserInfo(w http.ResponseWriter, req *http.Request) { 81 | 82 | user := api.GetUserFromContext(req) 83 | js, _ := json.Marshal(user) 84 | w.Header().Set("Content-Type", "application/json") 85 | w.Write(js) 86 | } 87 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/markcheno/go-vue-starter", 3 | "GoVersion": "go1.8", 4 | "GodepVersion": "v79", 5 | "Deps": [ 6 | { 7 | "ImportPath": "github.com/auth0/go-jwt-middleware", 8 | "Rev": "f3f7de3b9e394e3af3b88e1b9457f6f71d1ae0ac" 9 | }, 10 | { 11 | "ImportPath": "github.com/dgrijalva/jwt-go", 12 | "Comment": "v3.0.0-17-g2268707", 13 | "Rev": "2268707a8f0843315e2004ee4f1d021dc08baedf" 14 | }, 15 | { 16 | "ImportPath": "github.com/gorilla/mux", 17 | "Comment": "v1.3.0-5-g599cba5", 18 | "Rev": "599cba5e7b6137d46ddf58fb1765f5d928e69604" 19 | }, 20 | { 21 | "ImportPath": "github.com/jinzhu/gorm", 22 | "Comment": "v1.0-138-g45ccb13", 23 | "Rev": "45ccb134373e7d9fa76d5987a6fed6cd5a5adfd4" 24 | }, 25 | { 26 | "ImportPath": "github.com/jinzhu/gorm/dialects/postgres", 27 | "Comment": "v1.0-138-g45ccb13", 28 | "Rev": "45ccb134373e7d9fa76d5987a6fed6cd5a5adfd4" 29 | }, 30 | { 31 | "ImportPath": "github.com/jinzhu/gorm/dialects/sqlite", 32 | "Comment": "v1.0-138-g45ccb13", 33 | "Rev": "45ccb134373e7d9fa76d5987a6fed6cd5a5adfd4" 34 | }, 35 | { 36 | "ImportPath": "github.com/jinzhu/inflection", 37 | "Rev": "1c35d901db3da928c72a72d8458480cc9ade058f" 38 | }, 39 | { 40 | "ImportPath": "github.com/lib/pq", 41 | "Comment": "go1.0-cutoff-166-g2704adc", 42 | "Rev": "2704adc878c21e1329f46f6e56a1c387d788ff94" 43 | }, 44 | { 45 | "ImportPath": "github.com/lib/pq/hstore", 46 | "Comment": "go1.0-cutoff-166-g2704adc", 47 | "Rev": "2704adc878c21e1329f46f6e56a1c387d788ff94" 48 | }, 49 | { 50 | "ImportPath": "github.com/lib/pq/oid", 51 | "Comment": "go1.0-cutoff-166-g2704adc", 52 | "Rev": "2704adc878c21e1329f46f6e56a1c387d788ff94" 53 | }, 54 | { 55 | "ImportPath": "github.com/mattn/go-sqlite3", 56 | "Comment": "v1.2.0-80-gcf7286f", 57 | "Rev": "cf7286f069c3ef596efcc87781a4653a2e7607bd" 58 | }, 59 | { 60 | "ImportPath": "github.com/satori/go.uuid", 61 | "Comment": "v1.1.0-8-g5bf94b6", 62 | "Rev": "5bf94b69c6b68ee1b541973bb8e1144db23a194b" 63 | }, 64 | { 65 | "ImportPath": "github.com/urfave/negroni", 66 | "Comment": "v0.2.0-104-gc0db5fe", 67 | "Rev": "c0db5feaa33826cd5117930c8f4ee5c0f565eec6" 68 | }, 69 | { 70 | "ImportPath": "golang.org/x/crypto/bcrypt", 71 | "Rev": "9ef620b9ca2f82b55030ffd4f41327fa9e77a92c" 72 | }, 73 | { 74 | "ImportPath": "golang.org/x/crypto/blowfish", 75 | "Rev": "9ef620b9ca2f82b55030ffd4f41327fa9e77a92c" 76 | }, 77 | { 78 | "ImportPath": "golang.org/x/net/context", 79 | "Rev": "d1e1b351919c6738fdeb9893d5c998b161464f0c" 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /models/users.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | // postgress db driver 6 | _ "github.com/jinzhu/gorm/dialects/postgres" 7 | // import sqlite3 driver 8 | _ "github.com/jinzhu/gorm/dialects/sqlite" 9 | uuid "github.com/satori/go.uuid" 10 | "golang.org/x/crypto/bcrypt" 11 | ) 12 | 13 | // User struct 14 | type User struct { 15 | gorm.Model `json:"-"` 16 | Username string `gorm:"not null;unique" json:"username"` 17 | Password string `gorm:"not null" json:"-"` 18 | UUID string `gorm:"not null;unique" json:"uuid"` 19 | } 20 | 21 | // UserManager struct 22 | type UserManager struct { 23 | db *DB 24 | } 25 | 26 | // NewUserManager - Create a new *UserManager that can be used for managing users. 27 | func NewUserManager(db *DB) (*UserManager, error) { 28 | 29 | db.AutoMigrate(&User{}) 30 | 31 | usermgr := UserManager{} 32 | 33 | usermgr.db = db 34 | 35 | return &usermgr, nil 36 | } 37 | 38 | // HasUser - Check if the given username exists. 39 | func (state *UserManager) HasUser(username string) bool { 40 | if err := state.db.Where("username=?", username).Find(&User{}).Error; err != nil { 41 | return false 42 | } 43 | return true 44 | } 45 | 46 | // FindUser - 47 | func (state *UserManager) FindUser(username string) *User { 48 | user := User{} 49 | state.db.Where("username=?", username).Find(&user) 50 | return &user 51 | } 52 | 53 | // FindUserByUUID - 54 | func (state *UserManager) FindUserByUUID(uuid string) *User { 55 | user := User{} 56 | state.db.Where("uuid=?", uuid).Find(&user) 57 | return &user 58 | } 59 | 60 | // AddUser - Creates a user and hashes the password 61 | func (state *UserManager) AddUser(username, password string) *User { 62 | passwordHash := state.HashPassword(username, password) 63 | guid, _ := uuid.NewV4() 64 | user := &User{ 65 | Username: username, 66 | Password: passwordHash, 67 | UUID: guid.String(), 68 | } 69 | state.db.Create(&user) 70 | return user 71 | } 72 | 73 | // HashPassword - Hash the password (takes a username as well, it can be used for salting). 74 | func (state *UserManager) HashPassword(username, password string) string { 75 | hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 76 | if err != nil { 77 | panic("Permissions: bcrypt password hashing unsuccessful") 78 | } 79 | return string(hash) 80 | } 81 | 82 | // CheckPassword - compare a hashed password with a possible plaintext equivalent 83 | func (state *UserManager) CheckPassword(hashedPassword, password string) bool { 84 | if bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) != nil { 85 | return false 86 | } 87 | return true 88 | } 89 | -------------------------------------------------------------------------------- /client/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true 31 | }) 32 | 33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 34 | log: () => {} 35 | }) 36 | // force page reload when html-webpack-plugin template changes 37 | compiler.plugin('compilation', function (compilation) { 38 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 39 | hotMiddleware.publish({ action: 'reload' }) 40 | cb() 41 | }) 42 | }) 43 | 44 | // proxy api requests 45 | Object.keys(proxyTable).forEach(function (context) { 46 | var options = proxyTable[context] 47 | if (typeof options === 'string') { 48 | options = { target: options } 49 | } 50 | app.use(proxyMiddleware(options.filter || context, options)) 51 | }) 52 | 53 | // handle fallback for HTML5 history API 54 | app.use(require('connect-history-api-fallback')()) 55 | 56 | // serve webpack bundle output 57 | app.use(devMiddleware) 58 | 59 | // enable hot-reload and state-preserving 60 | // compilation error display 61 | app.use(hotMiddleware) 62 | 63 | // serve pure static assets 64 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 65 | app.use(staticPath, express.static('./static')) 66 | 67 | var uri = 'http://localhost:' + port 68 | 69 | var _resolve 70 | var readyPromise = new Promise(resolve => { 71 | _resolve = resolve 72 | }) 73 | 74 | console.log('> Starting dev server...') 75 | devMiddleware.waitUntilValid(() => { 76 | console.log('> Listening at ' + uri + '\n') 77 | // when env is testing, don't need open it 78 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 79 | opn(uri) 80 | } 81 | _resolve() 82 | }) 83 | 84 | var server = app.listen(port) 85 | 86 | module.exports = { 87 | mounted: readyPromise, 88 | close: () => { 89 | server.close() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /client/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /models/quotes.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/jinzhu/gorm" 7 | // postgress db driver 8 | _ "github.com/jinzhu/gorm/dialects/postgres" 9 | // import sqlite3 driver 10 | _ "github.com/jinzhu/gorm/dialects/sqlite" 11 | ) 12 | 13 | // Quote struct 14 | type Quote struct { 15 | gorm.Model 16 | Text string 17 | } 18 | 19 | // QuoteManager struct 20 | type QuoteManager struct { 21 | db *DB 22 | } 23 | 24 | // NewQuoteManager - Create a quote manager that can be used for retrieving quotes 25 | func NewQuoteManager(db *DB) (*QuoteManager, error) { 26 | 27 | db.AutoMigrate(&Quote{}) 28 | 29 | quotemgr := QuoteManager{} 30 | 31 | quotemgr.db = db 32 | 33 | return "emgr, nil 34 | } 35 | 36 | // RandomQuote - return a random quote 37 | func (qm *QuoteManager) RandomQuote() *Quote { 38 | quote := Quote{ 39 | Text: quotes[rand.Intn(len(quotes))], 40 | } 41 | return "e 42 | } 43 | 44 | var quotes = []string{ 45 | "Chuck Norris doesn't call the wrong number. You answer the wrong phone.", 46 | "Chuck Norris has already been to Mars; that's why there are no signs of life.", 47 | "Chuck Norris and Superman once fought each other on a bet. The loser had to start wearing his underwear on the outside of his pants.", 48 | "Some magicans can walk on water, Chuck Norris can swim through land.", 49 | "Chuck Norris once urinated in a semi truck's gas tank as a joke....that truck is now known as Optimus Prime.", 50 | "Chuck Norris doesn't flush the toilet, he scares the sh*t out of it", 51 | "Chuck Norris counted to infinity - twice.", 52 | "Chuck Norris can cut through a hot knife with butter", 53 | "Chuck Norris is the reason why Waldo is hiding.", 54 | "Death once had a near-Chuck Norris experience", 55 | "When the Boogeyman goes to sleep every night, he checks his closet for Chuck Norris.", 56 | "Chuck Norris can slam a revolving door.", 57 | "Chuck Norris once kicked a horse in the chin. Its decendants are known today as Giraffes.", 58 | "Chuck Norris will never have a heart attack. His heart isn't nearly foolish enough to attack him.", 59 | "Chuck Norris once got bit by a rattle snake........ After three days of pain and agony ..................the rattle snake died", 60 | "Chuck Norris can win a game of Connect Four in only three moves.", 61 | "When Chuck Norris does a pushup, he isn't lifting himself up, he's pushing the Earth down.", 62 | "There is no theory of evolution. Just a list of animals Chuck Norris allows to live.", 63 | "Chuck Norris can light a fire by rubbing two ice-cubes together.", 64 | "Chuck Norris doesn’t wear a watch. HE decides what time it is.", 65 | "The original title for Alien vs. Predator was Alien and Predator vs Chuck Norris.", 66 | "The film was cancelled shortly after going into preproduction. No one would pay nine dollars to see a movie fourteen seconds long.", 67 | "Chuck Norris doesn't read books. He stares them down until he gets the information he wants.", 68 | "Chuck Norris made a Happy Meal cry.", 69 | "Outer space exists because it's afraid to be on the same planet with Chuck Norris.", 70 | "If you spell Chuck Norris in Scrabble, you win. Forever.", 71 | "Chuck Norris can make snow angels on a concrete slab.", 72 | "Chuck Norris destroyed the periodic table, because Chuck Norris only recognizes the element of surprise.", 73 | "Chuck Norris has to use a stunt double when he does crying scenes.", 74 | "Chuck Norris' hand is the only hand that can beat a Royal Flush.", 75 | "There is no theory of evolution. Just a list of creatures Chuck Norris has allowed to live.", 76 | "Chuck Norris does not sleep. He waits.", 77 | "Chuck Norris tells a GPS which way to go.", 78 | "Some people wear Superman pajamas. Superman wears Chuck Norris pajamas.", 79 | "Chuck Norris's tears cure cancer ..... to bad he has never cried", 80 | "Chuck Norris doesn't breathe, he holds air hostage.", 81 | "Chuck Norris had a staring contest with Medusa, and won.", 82 | "When life hands Chuck Norris lemons, he makes orange juice.", 83 | "When Chuck Norris goes on a picnic, the ants bring him food.", 84 | "Chuck Norris gives Freddy Krueger nightmares.", 85 | "They once made a Chuck Norris toilet paper, but there was a problem: It wouldn't take shit from anybody.", 86 | "Chuck Norris can punch a cyclops between the eyes.", 87 | "Chuck Norris doesn't mow his lawn, he stands on the porch and dares it to grow", 88 | "Chuck Norris put out a forest fire. using only gasoline", 89 | "Chuck Norris CAN believe it's not butter.", 90 | "Custom t-shirts provided by Spreadshirt", 91 | } 92 | --------------------------------------------------------------------------------