├── README.md ├── views ├── about.pug ├── users.pug ├── data │ └── magnifying-glass.png ├── styles │ ├── src │ │ ├── navbar.css │ │ └── home.css │ └── style.css ├── layouts │ ├── mixins.pug │ └── main.pug ├── scripts │ ├── script.js │ └── src │ │ └── home.js ├── partials │ └── navbar.pug └── home.pug ├── .gitignore ├── database ├── models.js └── database.js ├── routes ├── utils │ └── navbar.js ├── error.js ├── root.js ├── users.js └── rest.js ├── test └── navbar-generate-links.test.js ├── main.js ├── LICENSE ├── package.json └── Gruntfile.js /README.md: -------------------------------------------------------------------------------- 1 | # Status Quo-ra 2 | -------------------------------------------------------------------------------- /views/about.pug: -------------------------------------------------------------------------------- 1 | extend layouts/main.pug 2 | 3 | block contents 4 | h1 About 5 | -------------------------------------------------------------------------------- /views/users.pug: -------------------------------------------------------------------------------- 1 | extend layouts/main.pug 2 | 3 | block contents 4 | h1 Users 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore packages installed by npm 2 | 3 | node_modules 4 | *~ 5 | 6 | # Shhh! 7 | **/credentials.js 8 | -------------------------------------------------------------------------------- /views/data/magnifying-glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kaelinator/Status-Quo-ra/development/views/data/magnifying-glass.png -------------------------------------------------------------------------------- /database/models.js: -------------------------------------------------------------------------------- 1 | const db = require('./database.js') 2 | 3 | const models = module.exports = { 4 | 5 | /* additional collections go here */ 6 | profiles: db.get('profiles') 7 | } 8 | -------------------------------------------------------------------------------- /routes/utils/navbar.js: -------------------------------------------------------------------------------- 1 | const links = exports.links = [ 2 | { id: 0, text: 'Home', href: '/' }, 3 | { id: 1, text: 'About', href: '/about' }, 4 | { id: 2, text: 'Users', href: '/users' } 5 | ] 6 | -------------------------------------------------------------------------------- /database/database.js: -------------------------------------------------------------------------------- 1 | const credentials = require('./credentials.js') 2 | 3 | const db = module.exports = require('monk')(credentials.uri(), (err) => { 4 | if (err) throw new Error('Couldn\'t connect to database') 5 | }) 6 | -------------------------------------------------------------------------------- /routes/error.js: -------------------------------------------------------------------------------- 1 | 2 | const errors = module.exports = {} 3 | 4 | errors.internal = (err, req, res, next) => { 5 | res.status(500) 6 | .send(res) 7 | } 8 | 9 | errors.client = (req, res) => { 10 | res.status(404) 11 | .send('400 : Not found!') 12 | } 13 | -------------------------------------------------------------------------------- /views/styles/src/navbar.css: -------------------------------------------------------------------------------- 1 | nav.main-nav { 2 | 3 | background-color: #fff; 4 | -webkit-box-shadow: 0 3px 2px -2px rgba(200, 200, 200, 0.2); 5 | -moz-box-shadow: 0 3px 2px -2px rgba(200, 200, 200, 0.2); 6 | box-shadow: 0 3px 2px -2px rgba(200, 200, 200, 0.2); 7 | } -------------------------------------------------------------------------------- /views/layouts/mixins.pug: -------------------------------------------------------------------------------- 1 | mixin printUser(user) 2 | h4 #{user.name} 3 | 4 | mixin populateNavbar(links, pageId) 5 | each link in links 6 | if link.id === pageId 7 | li.nav-item.active 8 | a.nav-link(href=`${link.href}`) #{link.text} 9 | else 10 | li.nav-item 11 | a.nav-link(href=`${link.href}`) #{link.text} 12 | -------------------------------------------------------------------------------- /views/styles/src/home.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | 3 | color: #B92B27; 4 | } 5 | 6 | .tagline { 7 | 8 | font-family: Georgia, Times; 9 | color: #999; 10 | } 11 | 12 | body { 13 | 14 | background-color: #fafafa; 15 | } 16 | 17 | .card { 18 | background-color: #fff; 19 | } 20 | 21 | #suggestions { 22 | 23 | position: absolute; 24 | } -------------------------------------------------------------------------------- /test/navbar-generate-links.test.js: -------------------------------------------------------------------------------- 1 | // const test = require('tape').test 2 | // const navbar = require('../routes/utils/navbar.js') 3 | 4 | // test('generateLinks', (t) => { 5 | // const links = navbar.generateLinks(0) 6 | // links.forEach((link) => t.equal(link.active, link.id === 0, 'should set the correct link to active')) 7 | // t.end() 8 | // }) 9 | -------------------------------------------------------------------------------- /views/styles/style.css: -------------------------------------------------------------------------------- 1 | .logo{color:#b92b27}.tagline{font-family:Georgia,Times;color:#999}body{background-color:#fafafa}.card{background-color:#fff}#suggestions{position:absolute}nav.main-nav{background-color:#fff;-webkit-box-shadow:0 3px 2px -2px rgba(200,200,200,.2);-moz-box-shadow:0 3px 2px -2px rgba(200,200,200,.2);box-shadow:0 3px 2px -2px rgba(200,200,200,.2)} -------------------------------------------------------------------------------- /views/scripts/script.js: -------------------------------------------------------------------------------- 1 | const suggest=s=>{const t=$("#suggestions").empty();s.forEach(s=>{$(t).append(`
  • ${s.user}
  • `)})};$("#searchBar").on("input focus",function(){const s=$(this).val();$.ajax({method:"GET",url:"/rest",data:{query:s}}).done(suggest).fail(s=>{console.log("err",s)})}),$("#searchBar").on("focusout",function(){$("#suggestions").empty()}); -------------------------------------------------------------------------------- /views/partials/navbar.pug: -------------------------------------------------------------------------------- 1 | nav.navbar.navbar-toggleable-md.navbar-light.sticky-top.main-nav 2 | button.navbar-toggler.navbar-toggler-right(type='button', data-toggle='collapse', data-target='#navigate') 3 | img.navbar-toggler-icon(src='#') 4 | a.navbar-brand(href='#') Status Quo-ra 5 | 6 | div.collapse.navbar-collapse(id='navigate') 7 | ul.navbar-nav.mr-auto 8 | +populateNavbar(navLinks, pageId) 9 | -------------------------------------------------------------------------------- /routes/root.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require('express').Router() 2 | const navbar = require('./utils/navbar.js') 3 | 4 | router.route('/') 5 | .get(home) 6 | 7 | router.route('/about') 8 | .get(about) 9 | 10 | function home(req, res, next) { 11 | 12 | res.render('home.pug', { navLinks: navbar.links, pageId: 0 }) 13 | } 14 | 15 | function about(req, res, next) { 16 | res.render('about.pug', { navLinks: navbar.links, pageId: 1 }) 17 | } 18 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require('express').Router() 2 | const navbar = require('./utils/navbar.js') 3 | // const {profiles} = require('../database/models.js') 4 | 5 | router.route('/:query?') 6 | .get(find) 7 | 8 | function find(req, res, next) { 9 | 10 | res.render('users.pug', { navLinks: navbar.links, pageId: 2 }) 11 | // profiles.find() 12 | // .then((u) => res.render('users.pug', {results: u})) 13 | // .catch((err) => { next(err) }) 14 | } 15 | -------------------------------------------------------------------------------- /views/home.pug: -------------------------------------------------------------------------------- 1 | extend layouts/main.pug 2 | 3 | block contents 4 | .spacer.py-5 5 | .jumbotron.card.col-12.col-lg-6.col-xl-6.mx-auto 6 | 7 | h1.logo.text-center.py-5 Status Quo-ra 8 | 9 | p.tagline.text-center 10 | | Using Status Quo-ra, you gain access to users' growth statistics. 11 | 12 | .input-group 13 | .input-group-addon 14 | img(src='/data/magnifying-glass.png') 15 | input.form-control#searchBar(type='text' placeholder='Your favorite user') 16 | .suggestion 17 | #suggestions.list-group -------------------------------------------------------------------------------- /routes/rest.js: -------------------------------------------------------------------------------- 1 | const router = module.exports = require('express').Router() 2 | const {profiles} = require('../database/models.js') 3 | const _ = require('lodash') 4 | 5 | router.route('/') 6 | .get(grab) 7 | 8 | function grab(req, res, next) { 9 | 10 | if (req.query.query == '' || req.query.length > 64) 11 | return res.send([]) 12 | 13 | profiles.find({ user: { $regex: RegExp(`.*${_.escapeRegExp(req.query.query)}.*`, 'i') } }) 14 | .then((docs) => { res.send(docs) }) 15 | .catch((err) => { res.status(500).send(err) }) 16 | } -------------------------------------------------------------------------------- /views/scripts/src/home.js: -------------------------------------------------------------------------------- 1 | 2 | const suggest = (data) => { 3 | 4 | const list = $('#suggestions').empty() 5 | 6 | data.forEach((item) => { 7 | $(list).append(`
  • ${item.user}
  • `) 8 | }) 9 | } 10 | 11 | $('#searchBar').on('input focus', function() { 12 | 13 | const search = $(this).val() 14 | 15 | $.ajax({ 16 | 17 | method: 'GET', 18 | url: '/rest', 19 | data: { query: search } 20 | }) 21 | .done(suggest) 22 | .fail((err) => { console.log('err', err) }) 23 | }) 24 | 25 | $('#searchBar').on('focusout', function() { 26 | 27 | $('#suggestions').empty() 28 | }) 29 | -------------------------------------------------------------------------------- /views/layouts/main.pug: -------------------------------------------------------------------------------- 1 | include mixins.pug 2 | doctype html 3 | html 4 | 5 | head 6 | meta(charset='utf-8') 7 | title #{title || ''} - Status Quo-ra 8 | 9 | body 10 | 11 | include ../partials/navbar.pug 12 | 13 | .container-fluid 14 | block contents 15 | 16 | .footer 17 | block footer 18 | 19 | script 20 | include ../../node_modules/jquery/dist/jquery.min.js 21 | include ../../node_modules/tether/dist/js/tether.min.js 22 | include ../../node_modules/bootstrap/dist/js/bootstrap.min.js 23 | include ../scripts/script.js 24 | 25 | style 26 | include ../../node_modules/bootstrap/dist/css/bootstrap.min.css 27 | include ../styles/style.css 28 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const pug = require('pug') 3 | const path = require('path') 4 | const error = require('./routes/error.js') 5 | 6 | const app = express() 7 | app.disable('x-powered-by') 8 | 9 | app.set('view engine', 'pug') 10 | app.set('port', process.env.PORT || 3000) 11 | 12 | app.use('/', require('./routes/root.js')) 13 | app.use('/users', require('./routes/users.js')) 14 | app.use('/rest', require('./routes/rest.js')) 15 | 16 | app.use('/data', express.static(path.join(__dirname, 'views', 'data'))) 17 | 18 | app.use(error.internal) 19 | app.use(error.client) 20 | 21 | app.listen(app.get('port'), () => console.log(`Listenting on port ${app.get('port')}`)) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kaelinator 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "status-quo-ra", 3 | "version": "1.0.0", 4 | "description": "Tracks user statistics", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Kaelinator/Status-Quo-ra.git" 12 | }, 13 | "keywords": [ 14 | "user", 15 | "stats", 16 | "track", 17 | "statistics" 18 | ], 19 | "author": "Kael Kirk", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/Kaelinator/Status-Quo-ra/issues" 23 | }, 24 | "homepage": "https://github.com/Kaelinator/Status-Quo-ra#readme", 25 | "dependencies": { 26 | "bootstrap": "^4.0.0-alpha.6", 27 | "express": "^4.15.4", 28 | "jquery": "^3.2.1", 29 | "lodash": "^4.17.4", 30 | "monk": "^6.0.3", 31 | "path": "^0.12.7", 32 | "pug": "^2.0.0-rc.3", 33 | "quora-scraper": "^1.0.2", 34 | "tether": "^1.4.0" 35 | }, 36 | "devDependencies": { 37 | "grunt": "^1.0.1", 38 | "grunt-contrib-cssmin": "^2.2.1", 39 | "grunt-contrib-uglify": "git://github.com/gruntjs/grunt-contrib-uglify.git#harmony", 40 | "grunt-contrib-watch": "^1.0.0", 41 | "grunt-express-server": "^0.5.3", 42 | "grunt-tape": "^0.1.0", 43 | "tape": "^4.8.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(grunt) { 3 | 4 | grunt.initConfig({ 5 | 6 | cssmin: { 7 | options: { 8 | mergeIntoShorthands: false, 9 | roundingPrecision: -1 10 | }, 11 | target: { 12 | files: { 13 | 'views/styles/style.css': ['views/styles/src/*.css'] 14 | } 15 | } 16 | }, 17 | 18 | express: { 19 | dev: { 20 | options: { 21 | script: 'main.js' 22 | } 23 | } 24 | }, 25 | 26 | tape: { 27 | 28 | options: { 29 | pretty: true, 30 | output: 'console' 31 | }, 32 | 33 | files: ['test/**/*.test.js'] 34 | }, 35 | 36 | uglify: { 37 | src: { 38 | files: { 39 | 'views/scripts/script.js': ['views/scripts/src/*.js'] 40 | } 41 | } 42 | }, 43 | 44 | watch: { 45 | 46 | options: { 47 | livereload: true 48 | }, 49 | 50 | scripts: { 51 | files: [ 52 | 'database/**/*.js', 53 | 'routes/**/*.js', 54 | 'views/**/*.js', 55 | 'views/**/*.css', 56 | 'views/**/*.pug', 57 | 'test/**/*.test.js', 58 | 'main.js' 59 | ], 60 | tasks: ['build'], 61 | options: { 62 | spawn: false 63 | } 64 | } 65 | } 66 | }) 67 | 68 | grunt.loadNpmTasks('grunt-express-server') 69 | grunt.loadNpmTasks('grunt-contrib-cssmin') 70 | grunt.loadNpmTasks('grunt-contrib-uglify') 71 | grunt.loadNpmTasks('grunt-contrib-watch') 72 | grunt.loadNpmTasks('grunt-tape') 73 | 74 | grunt.registerTask('default', ['build', 'watch']) 75 | grunt.registerTask('build', ['test', 'compress', 'reboot']) 76 | 77 | grunt.registerTask('compress', ['cssmin', 'uglify:src']) 78 | grunt.registerTask('reboot', ['express:dev:stop', 'express:dev']) 79 | grunt.registerTask('test', 'tape') 80 | } 81 | --------------------------------------------------------------------------------