diff --git a/client/.babelrc b/client/.babelrc
new file mode 100644
index 0000000..13f0e47
--- /dev/null
+++ b/client/.babelrc
@@ -0,0 +1,14 @@
+{
+ "presets": [
+ ["env", { "modules": false }],
+ "stage-2"
+ ],
+ "plugins": ["transform-runtime"],
+ "comments": false,
+ "env": {
+ "test": {
+ "presets": ["env", "stage-2"],
+ "plugins": [ "istanbul" ]
+ }
+ }
+}
diff --git a/client/.editorconfig b/client/.editorconfig
new file mode 100644
index 0000000..9d08a1a
--- /dev/null
+++ b/client/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/client/.eslintignore b/client/.eslintignore
new file mode 100644
index 0000000..34af377
--- /dev/null
+++ b/client/.eslintignore
@@ -0,0 +1,2 @@
+build/*.js
+config/*.js
diff --git a/client/.eslintrc.js b/client/.eslintrc.js
new file mode 100644
index 0000000..67c085d
--- /dev/null
+++ b/client/.eslintrc.js
@@ -0,0 +1,27 @@
+// http://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+ root: true,
+ parser: 'babel-eslint',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ env: {
+ browser: true,
+ },
+ // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
+ extends: 'standard',
+ // required to lint *.vue files
+ plugins: [
+ 'html'
+ ],
+ // add your custom rules here
+ 'rules': {
+ // allow paren-less arrow functions
+ 'arrow-parens': 0,
+ // allow async-await
+ 'generator-star-spacing': 0,
+ // allow debugger during development
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
+ }
+}
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..09552ad
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,6 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/client/.postcssrc.js b/client/.postcssrc.js
new file mode 100644
index 0000000..ea9a5ab
--- /dev/null
+++ b/client/.postcssrc.js
@@ -0,0 +1,8 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+ "plugins": {
+ // to edit target browsers: use "browserlist" field in package.json
+ "autoprefixer": {}
+ }
+}
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..565bd2a
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,21 @@
+# hacktivoverflow
+
+> A Vue.js project
+
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:8080
+npm run dev
+
+# build for production with minification
+npm run build
+
+# build for production and view the bundle analyzer report
+npm run build --report
+```
+
+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).
diff --git a/client/build/build.js b/client/build/build.js
new file mode 100644
index 0000000..6b8add1
--- /dev/null
+++ b/client/build/build.js
@@ -0,0 +1,35 @@
+require('./check-versions')()
+
+process.env.NODE_ENV = 'production'
+
+var ora = require('ora')
+var rm = require('rimraf')
+var path = require('path')
+var chalk = require('chalk')
+var webpack = require('webpack')
+var config = require('../config')
+var webpackConfig = require('./webpack.prod.conf')
+
+var spinner = ora('building for production...')
+spinner.start()
+
+rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
+ if (err) throw err
+ webpack(webpackConfig, function (err, stats) {
+ spinner.stop()
+ if (err) throw err
+ process.stdout.write(stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false
+ }) + '\n\n')
+
+ console.log(chalk.cyan(' Build complete.\n'))
+ console.log(chalk.yellow(
+ ' Tip: built files are meant to be served over an HTTP server.\n' +
+ ' Opening index.html over file:// won\'t work.\n'
+ ))
+ })
+})
diff --git a/client/build/check-versions.js b/client/build/check-versions.js
new file mode 100644
index 0000000..100f3a0
--- /dev/null
+++ b/client/build/check-versions.js
@@ -0,0 +1,48 @@
+var chalk = require('chalk')
+var semver = require('semver')
+var packageConfig = require('../package.json')
+var shell = require('shelljs')
+function exec (cmd) {
+ return require('child_process').execSync(cmd).toString().trim()
+}
+
+var versionRequirements = [
+ {
+ name: 'node',
+ currentVersion: semver.clean(process.version),
+ versionRequirement: packageConfig.engines.node
+ },
+]
+
+if (shell.which('npm')) {
+ versionRequirements.push({
+ name: 'npm',
+ currentVersion: exec('npm --version'),
+ versionRequirement: packageConfig.engines.npm
+ })
+}
+
+module.exports = function () {
+ var warnings = []
+ for (var i = 0; i < versionRequirements.length; i++) {
+ var mod = versionRequirements[i]
+ if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+ warnings.push(mod.name + ': ' +
+ chalk.red(mod.currentVersion) + ' should be ' +
+ chalk.green(mod.versionRequirement)
+ )
+ }
+ }
+
+ if (warnings.length) {
+ console.log('')
+ console.log(chalk.yellow('To use this template, you must update following to modules:'))
+ console.log()
+ for (var i = 0; i < warnings.length; i++) {
+ var warning = warnings[i]
+ console.log(' ' + warning)
+ }
+ console.log()
+ process.exit(1)
+ }
+}
diff --git a/client/build/dev-client.js b/client/build/dev-client.js
new file mode 100644
index 0000000..18aa1e2
--- /dev/null
+++ b/client/build/dev-client.js
@@ -0,0 +1,9 @@
+/* eslint-disable */
+require('eventsource-polyfill')
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
+
+hotClient.subscribe(function (event) {
+ if (event.action === 'reload') {
+ window.location.reload()
+ }
+})
diff --git a/client/build/dev-server.js b/client/build/dev-server.js
new file mode 100644
index 0000000..782dc6f
--- /dev/null
+++ b/client/build/dev-server.js
@@ -0,0 +1,89 @@
+require('./check-versions')()
+
+var config = require('../config')
+if (!process.env.NODE_ENV) {
+ process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
+}
+
+var opn = require('opn')
+var path = require('path')
+var express = require('express')
+var webpack = require('webpack')
+var proxyMiddleware = require('http-proxy-middleware')
+var webpackConfig = require('./webpack.dev.conf')
+
+// default port where dev server listens for incoming traffic
+var port = process.env.PORT || config.dev.port
+// automatically open browser, if not set will be false
+var autoOpenBrowser = !!config.dev.autoOpenBrowser
+// Define HTTP proxies to your custom API backend
+// https://github.com/chimurai/http-proxy-middleware
+var proxyTable = config.dev.proxyTable
+
+var app = express()
+var compiler = webpack(webpackConfig)
+
+var devMiddleware = require('webpack-dev-middleware')(compiler, {
+ publicPath: webpackConfig.output.publicPath,
+ quiet: true
+})
+
+var hotMiddleware = require('webpack-hot-middleware')(compiler, {
+ log: () => {}
+})
+// force page reload when html-webpack-plugin template changes
+compiler.plugin('compilation', function (compilation) {
+ compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
+ hotMiddleware.publish({ action: 'reload' })
+ cb()
+ })
+})
+
+// proxy api requests
+Object.keys(proxyTable).forEach(function (context) {
+ var options = proxyTable[context]
+ if (typeof options === 'string') {
+ options = { target: options }
+ }
+ app.use(proxyMiddleware(options.filter || context, options))
+})
+
+// handle fallback for HTML5 history API
+app.use(require('connect-history-api-fallback')())
+
+// serve webpack bundle output
+app.use(devMiddleware)
+
+// enable hot-reload and state-preserving
+// compilation error display
+app.use(hotMiddleware)
+
+// serve pure static assets
+var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
+app.use(staticPath, express.static('./static'))
+
+var uri = 'http://localhost:' + port
+
+var _resolve
+var readyPromise = new Promise(resolve => {
+ _resolve = resolve
+})
+
+console.log('> Starting dev server...')
+devMiddleware.waitUntilValid(() => {
+ console.log('> Listening at ' + uri + '\n')
+ // when env is testing, don't need open it
+ if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
+ opn(uri)
+ }
+ _resolve()
+})
+
+var server = app.listen(port)
+
+module.exports = {
+ ready: readyPromise,
+ close: () => {
+ server.close()
+ }
+}
diff --git a/client/build/utils.js b/client/build/utils.js
new file mode 100644
index 0000000..b1d54b4
--- /dev/null
+++ b/client/build/utils.js
@@ -0,0 +1,71 @@
+var path = require('path')
+var config = require('../config')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+
+exports.assetsPath = function (_path) {
+ var assetsSubDirectory = process.env.NODE_ENV === 'production'
+ ? config.build.assetsSubDirectory
+ : config.dev.assetsSubDirectory
+ return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+ options = options || {}
+
+ var cssLoader = {
+ loader: 'css-loader',
+ options: {
+ minimize: process.env.NODE_ENV === 'production',
+ sourceMap: options.sourceMap
+ }
+ }
+
+ // generate loader string to be used with extract text plugin
+ function generateLoaders (loader, loaderOptions) {
+ var loaders = [cssLoader]
+ if (loader) {
+ loaders.push({
+ loader: loader + '-loader',
+ options: Object.assign({}, loaderOptions, {
+ sourceMap: options.sourceMap
+ })
+ })
+ }
+
+ // Extract CSS when that option is specified
+ // (which is the case during production build)
+ if (options.extract) {
+ return ExtractTextPlugin.extract({
+ use: loaders,
+ fallback: 'vue-style-loader'
+ })
+ } else {
+ return ['vue-style-loader'].concat(loaders)
+ }
+ }
+
+ // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+ return {
+ css: generateLoaders(),
+ postcss: generateLoaders(),
+ less: generateLoaders('less'),
+ sass: generateLoaders('sass', { indentedSyntax: true }),
+ scss: generateLoaders('sass'),
+ stylus: generateLoaders('stylus'),
+ styl: generateLoaders('stylus')
+ }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+ var output = []
+ var loaders = exports.cssLoaders(options)
+ for (var extension in loaders) {
+ var loader = loaders[extension]
+ output.push({
+ test: new RegExp('\\.' + extension + '$'),
+ use: loader
+ })
+ }
+ return output
+}
diff --git a/client/build/vue-loader.conf.js b/client/build/vue-loader.conf.js
new file mode 100644
index 0000000..7aee79b
--- /dev/null
+++ b/client/build/vue-loader.conf.js
@@ -0,0 +1,12 @@
+var utils = require('./utils')
+var config = require('../config')
+var isProduction = process.env.NODE_ENV === 'production'
+
+module.exports = {
+ loaders: utils.cssLoaders({
+ sourceMap: isProduction
+ ? config.build.productionSourceMap
+ : config.dev.cssSourceMap,
+ extract: isProduction
+ })
+}
diff --git a/client/build/webpack.base.conf.js b/client/build/webpack.base.conf.js
new file mode 100644
index 0000000..f47b326
--- /dev/null
+++ b/client/build/webpack.base.conf.js
@@ -0,0 +1,67 @@
+var path = require('path')
+var utils = require('./utils')
+var config = require('../config')
+var vueLoaderConfig = require('./vue-loader.conf')
+
+function resolve (dir) {
+ return path.join(__dirname, '..', dir)
+}
+
+module.exports = {
+ entry: {
+ app: './src/main.js'
+ },
+ output: {
+ path: config.build.assetsRoot,
+ filename: '[name].js',
+ publicPath: process.env.NODE_ENV === 'production'
+ ? config.build.assetsPublicPath
+ : config.dev.assetsPublicPath
+ },
+ resolve: {
+ extensions: ['.js', '.vue', '.json'],
+ alias: {
+ 'vue$': 'vue/dist/vue.esm.js',
+ '@': resolve('src')
+ }
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(js|vue)$/,
+ loader: 'eslint-loader',
+ enforce: 'pre',
+ include: [resolve('src'), resolve('test')],
+ options: {
+ formatter: require('eslint-friendly-formatter')
+ }
+ },
+ {
+ test: /\.vue$/,
+ loader: 'vue-loader',
+ options: vueLoaderConfig
+ },
+ {
+ test: /\.js$/,
+ loader: 'babel-loader',
+ include: [resolve('src'), resolve('test')]
+ },
+ {
+ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+ loader: 'url-loader',
+ options: {
+ limit: 10000,
+ name: utils.assetsPath('img/[name].[hash:7].[ext]')
+ }
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+ loader: 'url-loader',
+ options: {
+ limit: 10000,
+ name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+ }
+ }
+ ]
+ }
+}
diff --git a/client/build/webpack.dev.conf.js b/client/build/webpack.dev.conf.js
new file mode 100644
index 0000000..5470402
--- /dev/null
+++ b/client/build/webpack.dev.conf.js
@@ -0,0 +1,35 @@
+var utils = require('./utils')
+var webpack = require('webpack')
+var config = require('../config')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+
+// add hot-reload related code to entry chunks
+Object.keys(baseWebpackConfig.entry).forEach(function (name) {
+ baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
+})
+
+module.exports = merge(baseWebpackConfig, {
+ module: {
+ rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
+ },
+ // cheap-module-eval-source-map is faster for development
+ devtool: '#cheap-module-eval-source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': config.dev.env
+ }),
+ // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoEmitOnErrorsPlugin(),
+ // https://github.com/ampedandwired/html-webpack-plugin
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ template: 'index.html',
+ inject: true
+ }),
+ new FriendlyErrorsPlugin()
+ ]
+})
diff --git a/client/build/webpack.prod.conf.js b/client/build/webpack.prod.conf.js
new file mode 100644
index 0000000..da44b65
--- /dev/null
+++ b/client/build/webpack.prod.conf.js
@@ -0,0 +1,120 @@
+var path = require('path')
+var utils = require('./utils')
+var webpack = require('webpack')
+var config = require('../config')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+
+var env = config.build.env
+
+var webpackConfig = merge(baseWebpackConfig, {
+ module: {
+ rules: utils.styleLoaders({
+ sourceMap: config.build.productionSourceMap,
+ extract: true
+ })
+ },
+ devtool: config.build.productionSourceMap ? '#source-map' : false,
+ output: {
+ path: config.build.assetsRoot,
+ filename: utils.assetsPath('js/[name].[chunkhash].js'),
+ chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
+ },
+ plugins: [
+ // http://vuejs.github.io/vue-loader/en/workflow/production.html
+ new webpack.DefinePlugin({
+ 'process.env': env
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ },
+ sourceMap: true
+ }),
+ // extract css into its own file
+ new ExtractTextPlugin({
+ filename: utils.assetsPath('css/[name].[contenthash].css')
+ }),
+ // Compress extracted CSS. We are using this plugin so that possible
+ // duplicated CSS from different components can be deduped.
+ new OptimizeCSSPlugin({
+ cssProcessorOptions: {
+ safe: true
+ }
+ }),
+ // generate dist index.html with correct asset hash for caching.
+ // you can customize output by editing /index.html
+ // see https://github.com/ampedandwired/html-webpack-plugin
+ new HtmlWebpackPlugin({
+ filename: config.build.index,
+ template: 'index.html',
+ inject: true,
+ minify: {
+ removeComments: true,
+ collapseWhitespace: true,
+ removeAttributeQuotes: true
+ // more options:
+ // https://github.com/kangax/html-minifier#options-quick-reference
+ },
+ // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+ chunksSortMode: 'dependency'
+ }),
+ // split vendor js into its own file
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ minChunks: function (module, count) {
+ // any required modules inside node_modules are extracted to vendor
+ return (
+ module.resource &&
+ /\.js$/.test(module.resource) &&
+ module.resource.indexOf(
+ path.join(__dirname, '../node_modules')
+ ) === 0
+ )
+ }
+ }),
+ // extract webpack runtime and module manifest to its own file in order to
+ // prevent vendor hash from being updated whenever app bundle is updated
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'manifest',
+ chunks: ['vendor']
+ }),
+ // copy custom static assets
+ new CopyWebpackPlugin([
+ {
+ from: path.resolve(__dirname, '../static'),
+ to: config.build.assetsSubDirectory,
+ ignore: ['.*']
+ }
+ ])
+ ]
+})
+
+if (config.build.productionGzip) {
+ var CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+ webpackConfig.plugins.push(
+ new CompressionWebpackPlugin({
+ asset: '[path].gz[query]',
+ algorithm: 'gzip',
+ test: new RegExp(
+ '\\.(' +
+ config.build.productionGzipExtensions.join('|') +
+ ')$'
+ ),
+ threshold: 10240,
+ minRatio: 0.8
+ })
+ )
+}
+
+if (config.build.bundleAnalyzerReport) {
+ var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+ webpackConfig.plugins.push(new BundleAnalyzerPlugin())
+}
+
+module.exports = webpackConfig
diff --git a/client/config/dev.env.js b/client/config/dev.env.js
new file mode 100644
index 0000000..efead7c
--- /dev/null
+++ b/client/config/dev.env.js
@@ -0,0 +1,6 @@
+var merge = require('webpack-merge')
+var prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+ NODE_ENV: '"development"'
+})
diff --git a/client/config/index.js b/client/config/index.js
new file mode 100644
index 0000000..196da1f
--- /dev/null
+++ b/client/config/index.js
@@ -0,0 +1,38 @@
+// see http://vuejs-templates.github.io/webpack for documentation.
+var path = require('path')
+
+module.exports = {
+ build: {
+ env: require('./prod.env'),
+ index: path.resolve(__dirname, '../dist/index.html'),
+ assetsRoot: path.resolve(__dirname, '../dist'),
+ assetsSubDirectory: 'static',
+ assetsPublicPath: '/',
+ productionSourceMap: true,
+ // Gzip off by default as many popular static hosts such as
+ // Surge or Netlify already gzip all static assets for you.
+ // Before setting to `true`, make sure to:
+ // npm install --save-dev compression-webpack-plugin
+ productionGzip: false,
+ productionGzipExtensions: ['js', 'css'],
+ // Run the build command with an extra argument to
+ // View the bundle analyzer report after build finishes:
+ // `npm run build --report`
+ // Set to `true` or `false` to always turn it on or off
+ bundleAnalyzerReport: process.env.npm_config_report
+ },
+ dev: {
+ env: require('./dev.env'),
+ port: 8080,
+ autoOpenBrowser: true,
+ assetsSubDirectory: 'static',
+ assetsPublicPath: '/',
+ proxyTable: {},
+ // CSS Sourcemaps off by default because relative paths are "buggy"
+ // with this option, according to the CSS-Loader README
+ // (https://github.com/webpack/css-loader#sourcemaps)
+ // In our experience, they generally work as expected,
+ // just be aware of this issue when enabling this option.
+ cssSourceMap: false
+ }
+}
diff --git a/client/config/prod.env.js b/client/config/prod.env.js
new file mode 100644
index 0000000..773d263
--- /dev/null
+++ b/client/config/prod.env.js
@@ -0,0 +1,3 @@
+module.exports = {
+ NODE_ENV: '"production"'
+}
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..8c816d6
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ hacktivoverflow
+
+
+
+
+
+
+
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 0000000..b44d805
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "hacktivoverflow",
+ "version": "1.0.0",
+ "description": "A Vue.js project",
+ "author": "hakiemaul ",
+ "private": true,
+ "scripts": {
+ "dev": "node build/dev-server.js",
+ "start": "node build/dev-server.js",
+ "build": "node build/build.js",
+ "lint": "eslint --ext .js,.vue src"
+ },
+ "dependencies": {
+ "axios": "^0.16.2",
+ "bootstrap-vue": "^0.16.1",
+ "vue": "^2.3.3",
+ "vue-axios": "^2.0.2",
+ "vue-router": "^2.3.1"
+ },
+ "devDependencies": {
+ "autoprefixer": "^6.7.2",
+ "babel-core": "^6.22.1",
+ "babel-eslint": "^7.1.1",
+ "babel-loader": "^6.2.10",
+ "babel-plugin-transform-runtime": "^6.22.0",
+ "babel-preset-env": "^1.3.2",
+ "babel-preset-stage-2": "^6.22.0",
+ "babel-register": "^6.22.0",
+ "chalk": "^1.1.3",
+ "connect-history-api-fallback": "^1.3.0",
+ "copy-webpack-plugin": "^4.0.1",
+ "css-loader": "^0.28.0",
+ "eslint": "^3.19.0",
+ "eslint-friendly-formatter": "^2.0.7",
+ "eslint-loader": "^1.7.1",
+ "eslint-plugin-html": "^2.0.0",
+ "eslint-config-standard": "^6.2.1",
+ "eslint-plugin-promise": "^3.4.0",
+ "eslint-plugin-standard": "^2.0.1",
+ "eventsource-polyfill": "^0.9.6",
+ "express": "^4.14.1",
+ "extract-text-webpack-plugin": "^2.0.0",
+ "file-loader": "^0.11.1",
+ "friendly-errors-webpack-plugin": "^1.1.3",
+ "html-webpack-plugin": "^2.28.0",
+ "http-proxy-middleware": "^0.17.3",
+ "webpack-bundle-analyzer": "^2.2.1",
+ "semver": "^5.3.0",
+ "shelljs": "^0.7.6",
+ "opn": "^4.0.2",
+ "optimize-css-assets-webpack-plugin": "^1.3.0",
+ "ora": "^1.2.0",
+ "rimraf": "^2.6.0",
+ "url-loader": "^0.5.8",
+ "vue-loader": "^12.1.0",
+ "vue-style-loader": "^3.0.1",
+ "vue-template-compiler": "^2.3.3",
+ "webpack": "^2.6.1",
+ "webpack-dev-middleware": "^1.10.0",
+ "webpack-hot-middleware": "^2.18.0",
+ "webpack-merge": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 4.0.0",
+ "npm": ">= 3.0.0"
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/client/src/App.vue b/client/src/App.vue
new file mode 100644
index 0000000..8140749
--- /dev/null
+++ b/client/src/App.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/assets/logo.png b/client/src/assets/logo.png
new file mode 100644
index 0000000..f3d2503
Binary files /dev/null and b/client/src/assets/logo.png differ
diff --git a/client/src/components/Auth.vue b/client/src/components/Auth.vue
new file mode 100644
index 0000000..8b9ea34
--- /dev/null
+++ b/client/src/components/Auth.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
Name
+
+
+
+
Email
+
+
+
+
Password
+
+
+
Register
+
+
+
+
+
+
Email
+
+
+
+
Password
+
+
+
Login
+
+
+
+
Akun berhasil dibuat! Silakan login untuk mulai bergabung dalam diskusi!
+
+
+
Email atau password anda salah!
+
+
+
Gagal, cek kembali input anda.
+
+
+
+
+
+
+
+
diff --git a/client/src/components/EditThread.vue b/client/src/components/EditThread.vue
new file mode 100644
index 0000000..ee365c4
--- /dev/null
+++ b/client/src/components/EditThread.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/Hello.vue b/client/src/components/Hello.vue
new file mode 100644
index 0000000..2d80539
--- /dev/null
+++ b/client/src/components/Hello.vue
@@ -0,0 +1,53 @@
+
+
+
{{ msg }}
+
Essential Links
+
+
Ecosystem
+
+
+
+
+
+
+
+
diff --git a/client/src/components/MainPage.vue b/client/src/components/MainPage.vue
new file mode 100644
index 0000000..d68428d
--- /dev/null
+++ b/client/src/components/MainPage.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/NavBar.vue b/client/src/components/NavBar.vue
new file mode 100644
index 0000000..abd4d14
--- /dev/null
+++ b/client/src/components/NavBar.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+ Hacktivoverflow
+
+
+
+
+
+ Welcome, {{ user.name }}
+
+
+
+ User Menu
+
+
+ Profile
+ Signout
+
+ Login/Register
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/NewReply.vue b/client/src/components/NewReply.vue
new file mode 100644
index 0000000..c056ef2
--- /dev/null
+++ b/client/src/components/NewReply.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/NewThread.vue b/client/src/components/NewThread.vue
new file mode 100644
index 0000000..f6d145f
--- /dev/null
+++ b/client/src/components/NewThread.vue
@@ -0,0 +1,47 @@
+
+
+
Post a thread
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/Reply.vue b/client/src/components/Reply.vue
new file mode 100644
index 0000000..cc15ba4
--- /dev/null
+++ b/client/src/components/Reply.vue
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+ {{ response.upvotes.length - response.downvotes.length }}
+
+
+
+
+
+
+
+
+
{{ content }}
+
replied by: {{ response.creator.name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/Thread.vue b/client/src/components/Thread.vue
new file mode 100644
index 0000000..d65f379
--- /dev/null
+++ b/client/src/components/Thread.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
{{ article.upvotes.length - article.downvotes.length }}
+
votes
+
+
+
+
+
{{ article.replies.length }}
+
replies
+
+
+
+
+
+
created by {{ article.creator.name }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/ThreadDetail.vue b/client/src/components/ThreadDetail.vue
new file mode 100644
index 0000000..3e069a7
--- /dev/null
+++ b/client/src/components/ThreadDetail.vue
@@ -0,0 +1,164 @@
+
+
+
+
+
{{ thread.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ thread.upvotes.length - thread.downvotes.length }}
+
+
+
+
+
+
+
+
+
{{ content }}
+
asked by: {{ thread.creator.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ thread.replies.length }} replies
+ {{ thread.replies.length }} reply
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/ThreadList.vue b/client/src/components/ThreadList.vue
new file mode 100644
index 0000000..9d6d811
--- /dev/null
+++ b/client/src/components/ThreadList.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
diff --git a/client/src/main.js b/client/src/main.js
new file mode 100644
index 0000000..ec10971
--- /dev/null
+++ b/client/src/main.js
@@ -0,0 +1,15 @@
+// The Vue build version to load with the `import` command
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
+import Vue from 'vue'
+import App from './App'
+import router from './router'
+
+Vue.config.productionTip = false
+
+/* eslint-disable no-new */
+new Vue({
+ el: '#app',
+ router,
+ template: '',
+ components: { App }
+})
diff --git a/client/src/router/index.js b/client/src/router/index.js
new file mode 100644
index 0000000..1a791be
--- /dev/null
+++ b/client/src/router/index.js
@@ -0,0 +1,36 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import MainPage from '@/components/MainPage'
+import Auth from '@/components/Auth'
+import ThreadDetail from '@/components/ThreadDetail'
+import EditThread from '@/components/EditThread'
+import BootstrapVue from 'bootstrap-vue'
+import axios from 'axios'
+import VueAxios from 'vue-axios'
+import 'bootstrap/dist/css/bootstrap.css'
+import 'bootstrap-vue/dist/bootstrap-vue.css'
+
+Vue.use(BootstrapVue)
+Vue.use(VueAxios, axios)
+Vue.use(Router)
+
+export default new Router({
+ routes: [
+ {
+ path: '/',
+ component: MainPage
+ },
+ {
+ path: '/registration',
+ component: Auth
+ },
+ {
+ path: '/thread',
+ component: ThreadDetail
+ },
+ {
+ path: '/edit',
+ component: EditThread
+ }
+ ]
+})
diff --git a/client/static/.gitkeep b/client/static/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/server/.dockerignore b/server/.dockerignore
new file mode 100644
index 0000000..5171c54
--- /dev/null
+++ b/server/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
\ No newline at end of file
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 0000000..1dcef2d
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.env
\ No newline at end of file
diff --git a/server/Dockerfile b/server/Dockerfile
new file mode 100644
index 0000000..b5603d5
--- /dev/null
+++ b/server/Dockerfile
@@ -0,0 +1,15 @@
+FROM node:boron
+
+# Create app directory
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+# Install app dependencies
+COPY package.json /usr/src/app/
+RUN npm install
+
+# Bundle app source
+COPY . /usr/src/app
+
+EXPOSE 8080
+CMD [ "npm", "start" ]
\ No newline at end of file
diff --git a/server/app.js b/server/app.js
new file mode 100644
index 0000000..dfd6ae3
--- /dev/null
+++ b/server/app.js
@@ -0,0 +1,29 @@
+const express = require('express');
+const bodyParser = require('body-parser');
+var cors = require('cors');
+var app = express()
+var mongoose = require('mongoose');
+var users = require('./routes/users');
+var threads = require('./routes/threads');
+var index = require('./routes/index');
+
+var db_config = {
+ development: 'mongodb://localhost/hacktivoverflow',
+ test: 'mongodb://localhost/hacktivoverflow-test'
+}
+
+var current_env = app.settings.env
+
+mongoose.connect(db_config[current_env])
+
+app.use(cors());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+
+app.use('/', index)
+app.use('/api/users', users)
+app.use('/api/threads', threads)
+
+app.listen(3000)
+
+module.exports = app;
\ No newline at end of file
diff --git a/server/controllers/auth.js b/server/controllers/auth.js
new file mode 100644
index 0000000..5c854a6
--- /dev/null
+++ b/server/controllers/auth.js
@@ -0,0 +1,96 @@
+require('dotenv').config();
+const sec = process.env.TOKEN_SECRET;
+const saltRounds = Number(process.env.SALT_ROUNDS);
+
+var User = require('../models/user');
+var bcrypt = require('bcrypt');
+var jwt = require('jsonwebtoken');
+
+var login = function(req, res, next) {
+ var email = req.body.email;
+ var password = req.body.password;
+
+ User.findOne({ email: email }, function(err, user) {
+ if(err) res.send(err);
+ if(user) {
+ bcrypt.compare(password, user.password)
+ .then(result => {
+ if(result) {
+ var token = jwt.sign({id: user._id, name: user.name, email: user.email }, sec);
+ res.send({token: token})
+ } else {
+ res.send({ msg: 'Incorrect password' });
+ }
+ })
+ .catch(err => console.log(err))
+ } else res.send({ msg: 'No such user' })
+ })
+}
+
+var signup = function(req, res, next) {
+ var salt = bcrypt.genSaltSync(saltRounds);
+ var hash = bcrypt.hashSync(req.body.password, salt);
+
+ var newUser = new User({
+ name: req.body.name,
+ email: req.body.email,
+ password: hash
+ })
+ newUser.save((err, user) => {
+ if(err) {
+ res.send(err.errors)
+ } else res.send(user)
+ })
+}
+
+var userInfo = function(req, res, next) {
+ let token = req.body.token
+ if(token) {
+ jwt.verify(token, sec, (err, decoded) => {
+ if(!err) {
+ req.body.creator = decoded.id;
+ next()
+ } else {
+ res.send(err)
+ }
+ })
+ } else {
+ res.send({msg: 'Not logged in'})
+ }
+}
+
+var userData = function(req, res, next) {
+ let token = req.body.token
+
+ if(token) {
+ jwt.verify(token, sec, (err, decoded) => {
+ if(!err) {
+ res.send(decoded)
+ } else {
+ res.send(err)
+ }
+ })
+ } else {
+ res.send({msg: 'Not logged in'})
+ }
+}
+
+var authUser = function(req, res, next) {
+ let token = req.body.token
+
+ if(token) {
+ jwt.verify(token, sec, (err, decoded) => {
+ if(decoded.id == req.params.id) {
+ next()
+ } else {
+ res.send(err)
+ }
+ })
+ } else {
+ res.send({msg: 'Not logged in'})
+ }
+}
+
+module.exports = {
+ login, signup, userInfo, authUser, userData
+};
diff --git a/server/controllers/responsectrl.js b/server/controllers/responsectrl.js
new file mode 100644
index 0000000..6dc086d
--- /dev/null
+++ b/server/controllers/responsectrl.js
@@ -0,0 +1,120 @@
+var Response = require('../models/response');
+var Thread = require('../models/thread');
+
+var create = function(req, res) {
+ let newResponse = new Response({
+ responseContent: req.body.responseContent,
+ creator: req.body.creator,
+ parent: req.params.id,
+ createdAt: new Date()
+ })
+ newResponse.save((err, createdResponse) => {
+ if(err) {
+ res.send(err)
+ } else {
+ Thread.findById(req.params.id, (err, thread) => {
+ thread.replies.push(createdResponse)
+ thread.save((err, updatedThread) => {
+ res.send(err ? err : createdResponse)
+ })
+ })
+ }
+ })
+}
+
+var get = function(req, res) {
+ Response.find({ parent: req.params.id })
+ .populate('creator')
+ .exec(function (err, responses) {
+ res.send(err ? err : responses)
+ })
+}
+
+var getOne = function(req, res) {
+ Response.findById(req.params.repid)
+ .populate('creator')
+ .exec(function (err, responses) {
+ res.send(err ? err : responses)
+ })
+}
+
+var update = function(req, res) {
+ Response.findById(req.params.repid, (err, response) => {
+ if(response.creator == req.body.creator) {
+ response.responseContent = req.body.responseContent || response.responseContent
+ response.updatedAt = new Date()
+ response.save((err, editedResponse) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(editedResponse)
+ }
+ })
+ } else {
+ res.send('Not authorized')
+ }
+ })
+}
+
+var upvote = function(req, res) {
+ Response.findById(req.params.repid, (err, response) => {
+ if(req.body.creator) {
+ var idxUp = response.upvotes.indexOf(req.body.creator);
+ var idxDown = response.downvotes.indexOf(req.body.creator);
+ if(idxUp == -1 && idxDown == -1) {
+ response.upvotes.push(req.body.creator)
+ } else if (idxDown !== -1) {
+ response.downvotes.splice(idxDown, 1)
+ }
+ response.save((err, upvotedResponse) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(upvotedResponse)
+ }
+ })
+ } else {
+ res.send('Not authorized')
+ }
+ })
+}
+
+var downvote = function(req, res) {
+ Response.findById(req.params.repid, (err, response) => {
+ var idxUp = response.upvotes.indexOf(req.body.creator);
+ var idxDown = response.downvotes.indexOf(req.body.creator);
+ if(req.body.creator) {
+ if(idxUp == -1 && idxDown == -1) {
+ response.downvotes.push(req.body.creator)
+ } else if (idxUp !== -1) {
+ response.upvotes.splice(idxDown, 1)
+ }
+ response.save((err, downvotedResponse) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(downvotedResponse)
+ }
+ })
+ } else {
+ res.send('Not authorized')
+ }
+ })
+}
+
+var remove = function(req, res) {
+ Response.findOneAndRemove({_id: req.params.repid}, (err, response) => {
+ if(err) res.send(err)
+ Thread.findById(req.params.id, (err, thread) => {
+ let idx = thread.replies.indexOf(response._id)
+ thread.replies.splice(idx, 1)
+ thread.save((err, updatedThread) => {
+ res.send(err ? err : response)
+ })
+ })
+ })
+}
+
+module.exports = {
+ create, get, getOne, update, upvote, downvote, remove
+};
\ No newline at end of file
diff --git a/server/controllers/threadctrl.js b/server/controllers/threadctrl.js
new file mode 100644
index 0000000..0fa1eb8
--- /dev/null
+++ b/server/controllers/threadctrl.js
@@ -0,0 +1,110 @@
+var Thread = require('../models/thread');
+
+var create = function(req, res) {
+ let newThread = new Thread({
+ title: req.body.title,
+ threadContent: req.body.threadContent,
+ creator: req.body.creator,
+ createdAt: new Date()
+ })
+ newThread.save((err, createdThread) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(createdThread)
+ }
+ })
+}
+
+var get = function(req, res) {
+ Thread.find({})
+ .populate('creator')
+ .exec(function (err, threads) {
+ res.send(err ? err : threads)
+ })
+}
+
+var getOne = function(req, res) {
+ Thread.findById(req.params.id)
+ .populate('creator')
+ .populate('replies')
+ .exec(function (err, thread) {
+ res.send(err ? err : thread)
+ })
+}
+
+var update = function(req, res) {
+ Thread.findById(req.params.id, (err, thread) => {
+ if(thread.creator == req.body.creator) {
+ thread.title = req.body.title || thread.title
+ thread.threadContent = req.body.threadContent || thread.threadContent
+ thread.updatedAt = new Date()
+ thread.save((err, editedThread) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(editedThread)
+ }
+ })
+ } else {
+ res.send('Not authorized')
+ }
+ })
+}
+
+var upvote = function(req, res) {
+ Thread.findById(req.params.id, (err, thread) => {
+ if(req.body.creator) {
+ var idxUp = thread.upvotes.indexOf(req.body.creator);
+ var idxDown = thread.downvotes.indexOf(req.body.creator);
+ if(idxUp == -1 && idxDown == -1) {
+ thread.upvotes.push(req.body.creator)
+ } else if (idxDown !== -1) {
+ thread.downvotes.splice(idxDown, 1)
+ }
+ thread.save((err, upvotedThread) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(upvotedThread)
+ }
+ })
+ } else {
+ res.send('Not authorized')
+ }
+ })
+}
+
+var downvote = function(req, res) {
+ Thread.findById(req.params.id, (err, thread) => {
+ if(req.body.creator) {
+ var idxUp = thread.upvotes.indexOf(req.body.creator);
+ var idxDown = thread.downvotes.indexOf(req.body.creator);
+ if(idxUp == -1 && idxDown == -1) {
+ thread.downvotes.push(req.body.creator)
+ } else if (idxUp !== -1) {
+ thread.upvotes.splice(idxDown, 1)
+ }
+ thread.save((err, downvotedThread) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(downvotedThread)
+ }
+ })
+ } else {
+ res.send('Not authorized')
+ }
+ })
+}
+
+var remove = function(req, res) {
+ Thread.findOneAndRemove({_id: req.params.id}, (err, thread) => {
+ if(err) res.send(err)
+ res.send(thread)
+ })
+}
+
+module.exports = {
+ create, get, getOne, update, upvote, downvote, remove
+};
\ No newline at end of file
diff --git a/server/controllers/userctrl.js b/server/controllers/userctrl.js
new file mode 100644
index 0000000..e3ccff4
--- /dev/null
+++ b/server/controllers/userctrl.js
@@ -0,0 +1,68 @@
+require('dotenv').config();
+const saltRounds = Number(process.env.SALT_ROUNDS);
+
+var bcrypt = require('bcrypt');
+var User = require('../models/user');
+
+var create = function(req, res) {
+ var salt = bcrypt.genSaltSync(saltRounds);
+ var hash = bcrypt.hashSync(req.body.password, salt);
+ let newUser = new User({
+ name: req.body.name,
+ email: req.body.email,
+ password: hash
+ })
+ newUser.save((err, createdUser) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(createdUser)
+ }
+ })
+}
+
+var get = function(req, res) {
+ User.find(function (err, users) {
+ res.send(err ? err : users)
+ });
+}
+
+var getOne = function(req, res) {
+ User.findById(req.params.id, (err, user) => {
+ res.send(err ? err: user)
+ })
+}
+
+var update = function(req, res) {
+ User.findById(req.params.id, (err, user) => {
+ if(err) {
+ res.send(err)
+ } else {
+ if(req.body.password) {
+ var salt = bcrypt.genSaltSync(saltRounds);
+ var hash = bcrypt.hashSync(req.body.password, salt);
+ user.password = hash;
+ }
+ user.name = req.body.name || user.name
+ user.email = req.body.email || user.email
+ user.save((err, editedUser) => {
+ if(err) {
+ res.send(err)
+ } else {
+ res.send(editedUser)
+ }
+ })
+ }
+ })
+}
+
+var remove = function(req, res) {
+ User.findOneAndRemove({_id: req.params.id}, (err, user) => {
+ if(err) res.send(err)
+ res.send(user)
+ })
+}
+
+module.exports = {
+ create, get, getOne, update, remove
+};
\ No newline at end of file
diff --git a/server/models/response.js b/server/models/response.js
new file mode 100644
index 0000000..dd8a17a
--- /dev/null
+++ b/server/models/response.js
@@ -0,0 +1,19 @@
+var mongoose = require('mongoose');
+var Schema = mongoose.Schema;
+
+var responseSchema = mongoose.Schema({
+ responseContent: {
+ type: String,
+ required: [true, 'Please enter thread\'s title']
+ },
+ parent: { type: Schema.Types.ObjectId, ref: 'Thread' },
+ creator: { type: Schema.Types.ObjectId, ref: 'User' },
+ upvotes: [{ type: Schema.Types.ObjectId, ref: 'User' }],
+ downvotes: [{ type: Schema.Types.ObjectId, ref: 'User' }],
+ createdAt: Date,
+ updatedAt: Date
+});
+
+var Response = mongoose.model('Response', responseSchema);
+
+module.exports = Response;
\ No newline at end of file
diff --git a/server/models/thread.js b/server/models/thread.js
new file mode 100644
index 0000000..c9b5749
--- /dev/null
+++ b/server/models/thread.js
@@ -0,0 +1,23 @@
+var mongoose = require('mongoose');
+var Schema = mongoose.Schema;
+
+var threadSchema = mongoose.Schema({
+ title: {
+ type: String,
+ required: [true, 'Please enter thread\'s title']
+ },
+ threadContent: {
+ type: String,
+ required: [true, 'Please enter thread\'s content']
+ },
+ creator: { type: Schema.Types.ObjectId, ref: 'User' },
+ replies: [{type: Schema.Types.ObjectId, ref: 'Response' }],
+ upvotes: [{ type: Schema.Types.ObjectId, ref: 'User' }],
+ downvotes: [{ type: Schema.Types.ObjectId, ref: 'User' }],
+ createdAt: Date,
+ updatedAt: Date
+});
+
+var Thread = mongoose.model('Thread', threadSchema);
+
+module.exports = Thread;
\ No newline at end of file
diff --git a/server/models/user.js b/server/models/user.js
new file mode 100644
index 0000000..de8c035
--- /dev/null
+++ b/server/models/user.js
@@ -0,0 +1,24 @@
+var mongoose = require('mongoose');
+var validator = require('validator');
+
+var userSchema = mongoose.Schema({
+ name: {
+ type: String,
+ required: [true, 'Mohon masukkan nama anda']
+ },
+ password: {
+ type: String,
+ required: [true, 'Mohon masukkan kata sandi anda']
+ },
+ email: {
+ type: String,
+ required: true,
+ validate: function(v) {
+ return validator.isEmail(v);
+ }
+ }
+});
+
+var User = mongoose.model('User', userSchema);
+
+module.exports = User;
\ No newline at end of file
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000..e368739
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "hacktivoverflow",
+ "version": "1.0.0",
+ "description": "server for hacktivoverflow q&a app",
+ "main": "app.js",
+ "scripts": {
+ "start": "node app.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/hakiemaul/hacktivoverflow.git"
+ },
+ "keywords": [
+ "vue",
+ "component"
+ ],
+ "author": "hakiemaul",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/hakiemaul/hacktivoverflow/issues"
+ },
+ "homepage": "https://github.com/hakiemaul/hacktivoverflow#readme",
+ "dependencies": {
+ "bcrypt": "^1.0.2",
+ "body-parser": "^1.17.2",
+ "cors": "^2.8.3",
+ "dotenv": "^4.0.0",
+ "express": "^4.15.3",
+ "jsonwebtoken": "^7.4.1",
+ "mongoose": "^4.10.4",
+ "validator": "^7.0.0"
+ }
+}
diff --git a/server/routes/index.js b/server/routes/index.js
new file mode 100644
index 0000000..00c9fd0
--- /dev/null
+++ b/server/routes/index.js
@@ -0,0 +1,12 @@
+var express = require('express');
+var router = express.Router();
+
+var auth = require('../controllers/auth');
+
+router.post('/signup', auth.signup)
+
+router.post('/login', auth.login)
+
+router.post('/getdata', auth.userData)
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/routes/threads.js b/server/routes/threads.js
new file mode 100644
index 0000000..c1559f3
--- /dev/null
+++ b/server/routes/threads.js
@@ -0,0 +1,35 @@
+var express = require('express');
+var router = express.Router();
+var threadctrl = require('../controllers/threadctrl');
+var responsectrl = require('../controllers/responsectrl');
+var auth = require('../controllers/auth')
+
+router.get('/', threadctrl.get)
+
+router.get('/:id/replies', threadctrl.getOne)
+
+router.get('/:id/reply', responsectrl.get)
+
+router.get('/:id/reply/:repid', responsectrl.getOne)
+
+router.post('/', auth.userInfo, threadctrl.create)
+
+router.post('/:id/reply', auth.userInfo, responsectrl.create)
+
+router.put('/:id', auth.userInfo, threadctrl.update)
+
+router.put('/:id/reply/:repid', auth.userInfo, responsectrl.update)
+
+router.put('/:id/upvote', auth.userInfo, threadctrl.upvote)
+
+router.put('/:id/reply/:repid/upvote', auth.userInfo, responsectrl.upvote)
+
+router.put('/:id/downvote', auth.userInfo, threadctrl.downvote)
+
+router.put('/:id/reply/:repid/downvote', auth.userInfo, responsectrl.downvote)
+
+router.delete('/:id', threadctrl.remove)
+
+router.delete('/:id/reply/:repid', responsectrl.remove)
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/routes/users.js b/server/routes/users.js
new file mode 100644
index 0000000..94f7005
--- /dev/null
+++ b/server/routes/users.js
@@ -0,0 +1,14 @@
+var express = require('express');
+var router = express.Router();
+var userctrl = require('../controllers/userctrl');
+var auth = require('../controllers/auth')
+
+router.get('/', userctrl.get)
+
+router.post('/', userctrl.create)
+
+router.put('/:id', auth.authUser, userctrl.update)
+
+router.delete('/:id', auth.authUser, userctrl.remove)
+
+module.exports = router;
\ No newline at end of file