├── 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 |
--------------------------------------------------------------------------------