Showing preview only (233K chars total). Download the full file or copy to clipboard to get everything.
Repository: gi-no/paizaqa
Branch: master
Commit: c8bc49534afa
Files: 133
Total size: 202.3 KB
Directory structure:
gitextract_m_s_vier/
├── .bowerrc
├── .buildignore
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jscsrc
├── .travis.yml
├── .yo-rc.json
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── client/
│ ├── .htaccess
│ ├── .jshintrc
│ ├── app/
│ │ ├── account/
│ │ │ ├── account.js
│ │ │ ├── login/
│ │ │ │ ├── login.controller.js
│ │ │ │ └── login.html
│ │ │ ├── settings/
│ │ │ │ ├── settings.controller.js
│ │ │ │ └── settings.html
│ │ │ └── signup/
│ │ │ ├── signup.controller.js
│ │ │ └── signup.html
│ │ ├── admin/
│ │ │ ├── admin.controller.js
│ │ │ ├── admin.html
│ │ │ ├── admin.module.js
│ │ │ ├── admin.router.js
│ │ │ └── admin.scss
│ │ ├── app.constant.js
│ │ ├── app.js
│ │ ├── app.scss
│ │ ├── fromNow/
│ │ │ ├── fromNow.filter.js
│ │ │ └── fromNow.filter.spec.js
│ │ ├── questionsCreate/
│ │ │ ├── questionsCreate.controller.js
│ │ │ ├── questionsCreate.controller.spec.js
│ │ │ ├── questionsCreate.html
│ │ │ ├── questionsCreate.js
│ │ │ └── questionsCreate.scss
│ │ ├── questionsIndex/
│ │ │ ├── questionsIndex.controller.js
│ │ │ ├── questionsIndex.controller.spec.js
│ │ │ ├── questionsIndex.html
│ │ │ ├── questionsIndex.js
│ │ │ └── questionsIndex.scss
│ │ └── questionsShow/
│ │ ├── questionsShow.controller.js
│ │ ├── questionsShow.controller.spec.js
│ │ ├── questionsShow.html
│ │ ├── questionsShow.js
│ │ └── questionsShow.scss
│ ├── components/
│ │ ├── auth/
│ │ │ ├── auth.module.js
│ │ │ ├── auth.service.js
│ │ │ ├── interceptor.service.js
│ │ │ ├── router.decorator.js
│ │ │ └── user.service.js
│ │ ├── footer/
│ │ │ ├── footer.directive.js
│ │ │ ├── footer.html
│ │ │ └── footer.scss
│ │ ├── modal/
│ │ │ ├── modal.html
│ │ │ ├── modal.scss
│ │ │ └── modal.service.js
│ │ ├── mongoose-error/
│ │ │ └── mongoose-error.directive.js
│ │ ├── navbar/
│ │ │ ├── navbar.controller.js
│ │ │ ├── navbar.directive.js
│ │ │ └── navbar.html
│ │ ├── oauth-buttons/
│ │ │ ├── oauth-buttons.controller.js
│ │ │ ├── oauth-buttons.controller.spec.js
│ │ │ ├── oauth-buttons.directive.js
│ │ │ ├── oauth-buttons.directive.spec.js
│ │ │ ├── oauth-buttons.html
│ │ │ └── oauth-buttons.scss
│ │ ├── socket/
│ │ │ ├── socket.mock.js
│ │ │ └── socket.service.js
│ │ ├── ui-router/
│ │ │ └── ui-router.mock.js
│ │ └── util/
│ │ ├── util.module.js
│ │ └── util.service.js
│ ├── index.html
│ └── robots.txt
├── e2e/
│ ├── account/
│ │ ├── login/
│ │ │ ├── login.po.js
│ │ │ └── login.spec.js
│ │ ├── logout/
│ │ │ └── logout.spec.js
│ │ └── signup/
│ │ ├── signup.po.js
│ │ └── signup.spec.js
│ ├── components/
│ │ ├── navbar/
│ │ │ └── navbar.po.js
│ │ └── oauth-buttons/
│ │ └── oauth-buttons.po.js
│ └── main/
│ ├── main.po.js
│ └── main.spec.js
├── karma.conf.js
├── mocha.conf.js
├── package.json
├── protractor.conf.js
└── server/
├── .jshintrc
├── .jshintrc-spec
├── api/
│ ├── question/
│ │ ├── index.js
│ │ ├── question.controller.js
│ │ ├── question.events.js
│ │ ├── question.integration.js
│ │ ├── question.model.js
│ │ └── question.socket.js
│ ├── thing/
│ │ ├── index.js
│ │ ├── index.spec.js
│ │ ├── thing.controller.js
│ │ ├── thing.events.js
│ │ ├── thing.integration.js
│ │ ├── thing.model.js
│ │ └── thing.socket.js
│ └── user/
│ ├── index.js
│ ├── index.spec.js
│ ├── user.controller.js
│ ├── user.events.js
│ ├── user.integration.js
│ ├── user.model.js
│ └── user.model.spec.js
├── app.js
├── auth/
│ ├── auth.service.js
│ ├── facebook/
│ │ ├── index.js
│ │ └── passport.js
│ ├── google/
│ │ ├── index.js
│ │ └── passport.js
│ ├── index.js
│ ├── local/
│ │ ├── index.js
│ │ └── passport.js
│ └── twitter/
│ ├── index.js
│ └── passport.js
├── components/
│ └── errors/
│ └── index.js
├── config/
│ ├── environment/
│ │ ├── development.js
│ │ ├── index.js
│ │ ├── production.js
│ │ ├── shared.js
│ │ └── test.js
│ ├── express.js
│ ├── local.env.sample.js
│ ├── seed.js
│ └── socketio.js
├── index.js
├── routes.js
└── views/
└── 404.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .bowerrc
================================================
{
"directory": "client/bower_components"
}
================================================
FILE: .buildignore
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .gitattributes
================================================
* text=auto
# These files are text and should be normalized (Convert crlf => lf)
*.php text
*.css text
*.js text
*.htm text
*.html text
*.xml text
*.txt text
*.ini text
*.inc text
.htaccess text
# Denote all files that are truly binary and should not be modified.
# (binary is a macro for -text -diff)
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
# Documents
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
================================================
FILE: .gitignore
================================================
node_modules
public
.tmp
.sass-cache
.idea
client/bower_components
dist
/server/config/local.env.js
npm-debug.log
coverage
.cache
.config
================================================
FILE: .jscsrc
================================================
{
"excludeFiles": [
"client/app/app.constant.js"
],
"esnext": true,
"maximumLineLength": {
"value": 100,
"allowComments": true,
"allowRegex": true
},
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowNewlineBeforeBlockStatements": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforeBinaryOperators": [","],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"requireCommaBeforeLineBreak": true,
"requireLineFeedAtFileEnd": true,
"requireSpaceAfterBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
"requireSpaceBeforeBinaryOperators": ["?", ":", "+", "-", "/", "*", "%", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "&&", "||"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireSpaceBeforeBlockStatements": true,
"requireSpacesInConditionalExpression": {
"afterTest": true,
"beforeConsequent": true,
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"validateLineBreaks": "LF",
"validateParameterSeparator": ", "
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 4.2.3
matrix:
fast_finish: true
allow_failures:
- node_js: 5.1.1
before_script:
- npm install -g bower grunt-cli
- gem install sass
- bower install
services: mongodb
================================================
FILE: .yo-rc.json
================================================
{
"generator-angular-fullstack": {
"generatorVersion": "3.3.0",
"endpointDirectory": "server/api/",
"insertRoutes": true,
"registerRoutesFile": "server/routes.js",
"routesNeedle": "// Insert routes below",
"routesBase": "/api/",
"pluralizeRoutes": true,
"insertSockets": true,
"registerSocketsFile": "server/config/socketio.js",
"socketsNeedle": "// Insert sockets below",
"insertModels": true,
"registerModelsFile": "server/sqldb/index.js",
"modelsNeedle": "// Insert models below",
"filters": {
"js": true,
"babel": true,
"html": true,
"sass": true,
"uirouter": true,
"bootstrap": true,
"uibootstrap": true,
"socketio": true,
"auth": true,
"models": true,
"mongooseModels": true,
"mongoose": true,
"oauth": true,
"googleAuth": true,
"facebookAuth": true,
"twitterAuth": true,
"grunt": true,
"jasmine": true,
"mocha": false,
"should": false,
"expect": false
}
},
"generator-ng-component": {
"routeDirectory": "client/app/",
"directiveDirectory": "client/app/",
"filterDirectory": "client/app/",
"serviceDirectory": "client/app/",
"basePath": "client",
"moduleName": "",
"filters": [
"uirouter",
"jasmine",
"uirouter"
],
"extensions": [
"babel",
"js",
"html",
"scss"
],
"directiveSimpleTemplates": "",
"directiveComplexTemplates": "",
"filterTemplates": "",
"serviceTemplates": "",
"factoryTemplates": "",
"controllerTemplates": "",
"decoratorTemplates": "",
"providerTemplates": "",
"routeTemplates": ""
}
}
================================================
FILE: Gruntfile.js
================================================
// Generated on 2016-03-08 using generator-angular-fullstack 3.3.0
'use strict';
module.exports = function (grunt) {
var localConfig;
try {
localConfig = require('./server/config/local.env');
} catch(e) {
localConfig = {};
}
// Load grunt tasks automatically, when needed
require('jit-grunt')(grunt, {
express: 'grunt-express-server',
useminPrepare: 'grunt-usemin',
ngtemplates: 'grunt-angular-templates',
cdnify: 'grunt-google-cdn',
protractor: 'grunt-protractor-runner',
buildcontrol: 'grunt-build-control',
istanbul_check_coverage: 'grunt-mocha-istanbul',
ngconstant: 'grunt-ng-constant'
});
// Time how long tasks take. Can help when optimizing build times
require('time-grunt')(grunt);
// Define the configuration for all the tasks
grunt.initConfig({
// Project settings
pkg: grunt.file.readJSON('package.json'),
yeoman: {
// configurable paths
client: require('./bower.json').appPath || 'client',
server: 'server',
dist: 'dist'
},
express: {
options: {
port: process.env.PORT || 9000
},
dev: {
options: {
script: '<%= yeoman.server %>',
debug: true
}
},
prod: {
options: {
script: '<%= yeoman.dist %>/<%= yeoman.server %>'
}
}
},
open: {
server: {
url: 'http://localhost:<%= express.options.port %>'
}
},
watch: {
babel: {
files: ['<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js'],
tasks: ['newer:babel:client']
},
ngconstant: {
files: ['<%= yeoman.server %>/config/environment/shared.js'],
tasks: ['ngconstant']
},
injectJS: {
files: [
'<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js',
'!<%= yeoman.client %>/app/app.js'
],
tasks: ['injector:scripts']
},
injectCss: {
files: ['<%= yeoman.client %>/{app,components}/**/*.css'],
tasks: ['injector:css']
},
mochaTest: {
files: ['<%= yeoman.server %>/**/*.{spec,integration}.js'],
tasks: ['env:test', 'mochaTest']
},
jsTest: {
files: ['<%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js'],
tasks: ['newer:jshint:all', 'wiredep:test', 'karma']
},
injectSass: {
files: ['<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
tasks: ['injector:sass']
},
sass: {
files: ['<%= yeoman.client %>/{app,components}/**/*.{scss,sass}'],
tasks: ['sass', 'postcss']
},
gruntfile: {
files: ['Gruntfile.js']
},
livereload: {
files: [
'{.tmp,<%= yeoman.client %>}/{app,components}/**/*.{css,html}',
'{.tmp,<%= yeoman.client %>}/{app,components}/**/!(*.spec|*.mock).js',
'<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}'
],
options: {
livereload: true
}
},
express: {
files: ['<%= yeoman.server %>/**/*.{js,json}'],
tasks: ['express:dev', 'wait'],
options: {
livereload: true,
spawn: false //Without this option specified express won't be reloaded
}
},
bower: {
files: ['bower.json'],
tasks: ['wiredep']
},
},
// Make sure code styles are up to par and there are no obvious mistakes
jshint: {
options: {
jshintrc: '<%= yeoman.client %>/.jshintrc',
reporter: require('jshint-stylish')
},
server: {
options: {
jshintrc: '<%= yeoman.server %>/.jshintrc'
},
src: ['<%= yeoman.server %>/**/!(*.spec|*.integration).js']
},
serverTest: {
options: {
jshintrc: '<%= yeoman.server %>/.jshintrc-spec'
},
src: ['<%= yeoman.server %>/**/*.{spec,integration}.js']
},
all: ['<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock|app.constant).js'],
test: {
src: ['<%= yeoman.client %>/{app,components}/**/*.{spec,mock}.js']
}
},
jscs: {
options: {
config: ".jscsrc"
},
main: {
files: {
src: [
'<%= yeoman.client %>/app/**/*.js',
'<%= yeoman.server %>/**/*.js'
]
}
}
},
// Empties folders to start fresh
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'<%= yeoman.dist %>/!(.git*|.openshift|Procfile)**'
]
}]
},
server: '.tmp'
},
// Add vendor prefixed styles
postcss: {
options: {
map: true,
processors: [
require('autoprefixer')({browsers: ['last 2 version']})
]
},
dist: {
files: [{
expand: true,
cwd: '.tmp/',
src: '{,*/}*.css',
dest: '.tmp/'
}]
}
},
// Debugging with node inspector
'node-inspector': {
custom: {
options: {
'web-host': 'localhost'
}
}
},
// Use nodemon to run server in debug mode with an initial breakpoint
nodemon: {
debug: {
script: '<%= yeoman.server %>',
options: {
nodeArgs: ['--debug-brk'],
env: {
PORT: process.env.PORT || 9000
},
callback: function (nodemon) {
nodemon.on('log', function (event) {
console.log(event.colour);
});
// opens browser on initial server start
nodemon.on('config:update', function () {
setTimeout(function () {
require('open')('http://localhost:8080/debug?port=5858');
}, 500);
});
}
}
}
},
// Automatically inject Bower components into the app and karma.conf.js
wiredep: {
options: {
exclude: [
/bootstrap.js/,
'/json3/',
'/es5-shim/',
/font-awesome\.css/,
/bootstrap\.css/,
/bootstrap-sass-official/,
/bootstrap-social\.css/
]
},
client: {
src: '<%= yeoman.client %>/index.html',
ignorePath: '<%= yeoman.client %>/',
},
test: {
src: './karma.conf.js',
devDependencies: true
}
},
// Renames files for browser caching purposes
filerev: {
dist: {
src: [
'<%= yeoman.dist %>/<%= yeoman.client %>/!(bower_components){,*/}*.{js,css}',
'<%= yeoman.dist %>/<%= yeoman.client %>/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
'<%= yeoman.dist %>/<%= yeoman.client %>/assets/fonts/*'
]
}
},
// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
useminPrepare: {
html: ['<%= yeoman.client %>/index.html'],
options: {
dest: '<%= yeoman.dist %>/<%= yeoman.client %>'
}
},
// Performs rewrites based on rev and the useminPrepare configuration
usemin: {
html: ['<%= yeoman.dist %>/<%= yeoman.client %>/{,!(bower_components)/**/}*.html'],
css: ['<%= yeoman.dist %>/<%= yeoman.client %>/!(bower_components){,*/}*.css'],
js: ['<%= yeoman.dist %>/<%= yeoman.client %>/!(bower_components){,*/}*.js'],
options: {
assetsDirs: [
'<%= yeoman.dist %>/<%= yeoman.client %>',
'<%= yeoman.dist %>/<%= yeoman.client %>/assets/images'
],
// This is so we update image references in our ng-templates
patterns: {
js: [
[/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images']
]
}
}
},
// The following *-min tasks produce minified files in the dist folder
imagemin: {
dist: {
files: [{
expand: true,
cwd: '<%= yeoman.client %>/assets/images',
src: '{,*/}*.{png,jpg,jpeg,gif,svg}',
dest: '<%= yeoman.dist %>/<%= yeoman.client %>/assets/images'
}]
}
},
// Allow the use of non-minsafe AngularJS files. Automatically makes it
// minsafe compatible so Uglify does not destroy the ng references
ngAnnotate: {
dist: {
files: [{
expand: true,
cwd: '.tmp/concat',
src: '**/*.js',
dest: '.tmp/concat'
}]
}
},
// Dynamically generate angular constant `appConfig` from
// `server/config/environment/shared.js`
ngconstant: {
options: {
name: 'paizaqaApp.constants',
dest: '<%= yeoman.client %>/app/app.constant.js',
deps: [],
wrap: true,
configPath: '<%= yeoman.server %>/config/environment/shared'
},
app: {
constants: function() {
return {
appConfig: require('./' + grunt.config.get('ngconstant.options.configPath'))
};
}
}
},
// Package all the html partials into a single javascript payload
ngtemplates: {
options: {
// This should be the name of your apps angular module
module: 'paizaqaApp',
htmlmin: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true
},
usemin: 'app/app.js'
},
main: {
cwd: '<%= yeoman.client %>',
src: ['{app,components}/**/*.html'],
dest: '.tmp/templates.js'
},
tmp: {
cwd: '.tmp',
src: ['{app,components}/**/*.html'],
dest: '.tmp/tmp-templates.js'
}
},
// Replace Google CDN references
cdnify: {
dist: {
html: ['<%= yeoman.dist %>/<%= yeoman.client %>/*.html']
}
},
// Copies remaining files to places other tasks can use
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.client %>',
dest: '<%= yeoman.dist %>/<%= yeoman.client %>',
src: [
'*.{ico,png,txt}',
'.htaccess',
'bower_components/**/*',
'assets/images/{,*/}*.{webp}',
'assets/fonts/**/*',
'index.html'
]
}, {
expand: true,
cwd: '.tmp/images',
dest: '<%= yeoman.dist %>/<%= yeoman.client %>/assets/images',
src: ['generated/*']
}, {
expand: true,
dest: '<%= yeoman.dist %>',
src: [
'package.json',
'<%= yeoman.server %>/**/*',
'!<%= yeoman.server %>/config/local.env.sample.js'
]
}]
},
styles: {
expand: true,
cwd: '<%= yeoman.client %>',
dest: '.tmp/',
src: ['{app,components}/**/*.css']
}
},
buildcontrol: {
options: {
dir: '<%= yeoman.dist %>',
commit: true,
push: true,
connectCommits: false,
message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
},
heroku: {
options: {
remote: 'heroku',
branch: 'master'
}
},
openshift: {
options: {
remote: 'openshift',
branch: 'master'
}
}
},
// Run some tasks in parallel to speed up the build process
concurrent: {
pre: [
'injector:sass',
'ngconstant'
],
server: [
'newer:babel:client',
'sass',
],
test: [
'newer:babel:client',
'sass',
],
debug: {
tasks: [
'nodemon',
'node-inspector'
],
options: {
logConcurrentOutput: true
}
},
dist: [
'newer:babel:client',
'sass',
'imagemin'
]
},
// Test settings
karma: {
unit: {
configFile: 'karma.conf.js',
singleRun: true
}
},
mochaTest: {
options: {
reporter: 'spec',
require: 'mocha.conf.js',
timeout: 5000 // set default mocha spec timeout
},
unit: {
src: ['<%= yeoman.server %>/**/*.spec.js']
},
integration: {
src: ['<%= yeoman.server %>/**/*.integration.js']
}
},
mocha_istanbul: {
unit: {
options: {
excludes: ['**/*.{spec,mock,integration}.js'],
reporter: 'spec',
require: ['mocha.conf.js'],
mask: '**/*.spec.js',
coverageFolder: 'coverage/server/unit'
},
src: '<%= yeoman.server %>'
},
integration: {
options: {
excludes: ['**/*.{spec,mock,integration}.js'],
reporter: 'spec',
require: ['mocha.conf.js'],
mask: '**/*.integration.js',
coverageFolder: 'coverage/server/integration'
},
src: '<%= yeoman.server %>'
}
},
istanbul_check_coverage: {
default: {
options: {
coverageFolder: 'coverage/**',
check: {
lines: 80,
statements: 80,
branches: 80,
functions: 80
}
}
}
},
protractor: {
options: {
configFile: 'protractor.conf.js'
},
chrome: {
options: {
args: {
browser: 'chrome'
}
}
}
},
env: {
test: {
NODE_ENV: 'test'
},
prod: {
NODE_ENV: 'production'
},
all: localConfig
},
// Compiles ES6 to JavaScript using Babel
babel: {
options: {
sourceMap: true,
optional: [
'es7.classProperties'
]
},
client: {
files: [{
expand: true,
cwd: '<%= yeoman.client %>',
src: ['{app,components}/**/!(*.spec).js'],
dest: '.tmp'
}]
},
server: {
options: {
optional: ['runtime']
},
files: [{
expand: true,
cwd: '<%= yeoman.server %>',
src: ['**/*.js'],
dest: '<%= yeoman.dist %>/<%= yeoman.server %>'
}]
}
},
// Compiles Sass to CSS
sass: {
server: {
options: {
compass: false
},
files: {
'.tmp/app/app.css' : '<%= yeoman.client %>/app/app.scss'
}
}
},
injector: {
options: {},
// Inject application script files into index.html (doesn't include bower)
scripts: {
options: {
transform: function(filePath) {
var yoClient = grunt.config.get('yeoman.client');
filePath = filePath.replace('/' + yoClient + '/', '');
filePath = filePath.replace('/.tmp/', '');
return '<script src="' + filePath + '"></script>';
},
sort: function(a, b) {
var module = /\.module\.js$/;
var aMod = module.test(a);
var bMod = module.test(b);
// inject *.module.js first
return (aMod === bMod) ? 0 : (aMod ? -1 : 1);
},
starttag: '<!-- injector:js -->',
endtag: '<!-- endinjector -->'
},
files: {
'<%= yeoman.client %>/index.html': [
[
'<%= yeoman.client %>/{app,components}/**/!(*.spec|*.mock).js',
'!{.tmp,<%= yeoman.client %>}/app/app.{js,ts}'
]
]
}
},
// Inject component scss into app.scss
sass: {
options: {
transform: function(filePath) {
var yoClient = grunt.config.get('yeoman.client');
filePath = filePath.replace('/' + yoClient + '/app/', '');
filePath = filePath.replace('/' + yoClient + '/components/', '../components/');
return '@import \'' + filePath + '\';';
},
starttag: '// injector',
endtag: '// endinjector'
},
files: {
'<%= yeoman.client %>/app/app.scss': [
'<%= yeoman.client %>/{app,components}/**/*.{scss,sass}',
'!<%= yeoman.client %>/app/app.{scss,sass}'
]
}
},
// Inject component css into index.html
css: {
options: {
transform: function(filePath) {
var yoClient = grunt.config.get('yeoman.client');
filePath = filePath.replace('/' + yoClient + '/', '');
filePath = filePath.replace('/.tmp/', '');
return '<link rel="stylesheet" href="' + filePath + '">';
},
starttag: '<!-- injector:css -->',
endtag: '<!-- endinjector -->'
},
files: {
'<%= yeoman.client %>/index.html': [
'<%= yeoman.client %>/{app,components}/**/*.css'
]
}
}
},
});
// Used for delaying livereload until after server has restarted
grunt.registerTask('wait', function () {
grunt.log.ok('Waiting for server reload...');
var done = this.async();
setTimeout(function () {
grunt.log.writeln('Done waiting!');
done();
}, 1500);
});
grunt.registerTask('express-keepalive', 'Keep grunt running', function() {
this.async();
});
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']);
}
if (target === 'debug') {
return grunt.task.run([
'clean:server',
'env:all',
'concurrent:pre',
'concurrent:server',
'injector',
'wiredep:client',
'postcss',
'concurrent:debug'
]);
}
grunt.task.run([
'clean:server',
'env:all',
'concurrent:pre',
'concurrent:server',
'injector',
'wiredep:client',
'postcss',
'express:dev',
'wait',
'open',
'watch'
]);
});
grunt.registerTask('server', function () {
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
grunt.task.run(['serve']);
});
grunt.registerTask('test', function(target, option) {
if (target === 'server') {
return grunt.task.run([
'env:all',
'env:test',
'mochaTest:unit',
'mochaTest:integration'
]);
}
else if (target === 'client') {
return grunt.task.run([
'clean:server',
'env:all',
'concurrent:pre',
'concurrent:test',
'injector',
'postcss',
'wiredep:test',
'karma'
]);
}
else if (target === 'e2e') {
if (option === 'prod') {
return grunt.task.run([
'build',
'env:all',
'env:prod',
'express:prod',
'protractor'
]);
}
else {
return grunt.task.run([
'clean:server',
'env:all',
'env:test',
'concurrent:pre',
'concurrent:test',
'injector',
'wiredep:client',
'postcss',
'express:dev',
'protractor'
]);
}
}
else if (target === 'coverage') {
if (option === 'unit') {
return grunt.task.run([
'env:all',
'env:test',
'mocha_istanbul:unit'
]);
}
else if (option === 'integration') {
return grunt.task.run([
'env:all',
'env:test',
'mocha_istanbul:integration'
]);
}
else if (option === 'check') {
return grunt.task.run([
'istanbul_check_coverage'
]);
}
else {
return grunt.task.run([
'env:all',
'env:test',
'mocha_istanbul',
'istanbul_check_coverage'
]);
}
}
else grunt.task.run([
'test:server',
'test:client'
]);
});
grunt.registerTask('build', [
'clean:dist',
'concurrent:pre',
'concurrent:dist',
'injector',
'wiredep:client',
'useminPrepare',
'postcss',
'ngtemplates',
'concat',
'ngAnnotate',
'copy:dist',
'babel:server',
'cdnify',
'cssmin',
'uglify',
'filerev',
'usemin'
]);
grunt.registerTask('default', [
'newer:jshint',
'test',
'build'
]);
};
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Gino, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# PaizaQA
PaizaQA is a Open Source QA service(like StackOverflow) using MEAN stack.
This project was generated with the [Angular Full-Stack Generator](https://github.com/DaftMonk/generator-angular-fullstack) version 3.3.0.
## Blog article
The following blog article explains how to build the QA service using MEAN stack.
English: [Building a QA web service in an hour - MEAN stack development(3)](http://engineering.paiza.io/entry/2016/03/10/115345)
Japanese: [Webサービスを作りたい人に最適、たった1時間でJSベースのQAサイトを作る方法 - MEANスタック開発(3)](http://paiza.hatenablog.com/entry/meanstack_howto_3)
## Demo
[http://paizaqa.herokuapp.com](http://paizaqa.herokuapp.com)
## Getting Started
### Prerequisites
- [Git](https://git-scm.com/)
- [Node.js and npm](nodejs.org) Node ^4.2.3, npm ^2.14.7
- [Bower](bower.io) (`npm install --global bower`)
- [Ruby](https://www.ruby-lang.org) and then `gem install sass`
- [Grunt](http://gruntjs.com/) (`npm install --global grunt-cli`)
- [MongoDB](https://www.mongodb.org/) - Keep a running daemon with `mongod`
### Developing
1. Run `npm install` to install server dependencies.
2. Run `bower install` to install front-end dependencies.
3. Run `mongod` in a separate shell to keep an instance of the MongoDB Daemon running
4. Run `grunt serve` to start the development server. It should automatically open the client in your browser when ready.
## Build & development
Run `grunt build` for building and `grunt serve` for preview.
## Testing
Running `npm test` will run the unit tests with karma.
================================================
FILE: bower.json
================================================
{
"name": "paizaqa",
"version": "0.0.0",
"dependencies": {
"angular": "~1.4.0",
"json3": "~3.3.1",
"es5-shim": "~3.0.1",
"bootstrap-sass-official": "~3.1.1",
"bootstrap": "~3.1.1",
"bootstrap-social": "~4.9.1",
"angular-resource": "~1.4.0",
"angular-cookies": "~1.4.0",
"angular-sanitize": "~1.4.0",
"angular-bootstrap": "~0.13.0",
"font-awesome": ">=4.1.0",
"lodash": "~2.4.1",
"angular-socket-io": "~0.7.0",
"angular-ui-router": "~0.2.15",
"angular-validation-match": "~1.5.2",
"angular-pagedown": "^0.4.4",
"ng-tags-input": "^3.0.0",
"angular-messages": "^1.5.0",
"moment": "momentjs#^2.12.0",
"ngInfiniteScroll": "^1.2.2"
},
"devDependencies": {
"angular-mocks": "~1.4.0"
},
"overrides": {
"pagedown": {
"main": [
"Markdown.Converter.js",
"Markdown.Sanitizer.js",
"Markdown.Extra.js",
"Markdown.Editor.js",
"wmd-buttons.png"
]
}
}
}
================================================
FILE: client/.htaccess
================================================
# Apache Configuration File
# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
# to the main server config file (usually called `httpd.conf`), you should add
# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
# ##############################################################################
# # CROSS-ORIGIN RESOURCE SHARING (CORS) #
# ##############################################################################
# ------------------------------------------------------------------------------
# | Cross-domain AJAX requests |
# ------------------------------------------------------------------------------
# Enable cross-origin AJAX requests.
# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
# http://enable-cors.org/
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# ------------------------------------------------------------------------------
# | CORS-enabled images |
# ------------------------------------------------------------------------------
# Send the CORS header for images when browsers request it.
# https://developer.mozilla.org/en/CORS_Enabled_Image
# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
<FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
</FilesMatch>
</IfModule>
</IfModule>
# ------------------------------------------------------------------------------
# | Web fonts access |
# ------------------------------------------------------------------------------
# Allow access from all domains for web fonts
<IfModule mod_headers.c>
<FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
# ##############################################################################
# # ERRORS #
# ##############################################################################
# ------------------------------------------------------------------------------
# | 404 error prevention for non-existing redirected folders |
# ------------------------------------------------------------------------------
# Prevent Apache from returning a 404 error for a rewrite if a directory
# with the same name does not exist.
# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
# http://www.webmasterworld.com/apache/3808792.htm
Options -MultiViews
# ------------------------------------------------------------------------------
# | Custom error messages / pages |
# ------------------------------------------------------------------------------
# You can customize what Apache returns to the client in case of an error (see
# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
ErrorDocument 404 /404.html
# ##############################################################################
# # INTERNET EXPLORER #
# ##############################################################################
# ------------------------------------------------------------------------------
# | Better website experience |
# ------------------------------------------------------------------------------
# Force IE to render pages in the highest available mode in the various
# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
<IfModule mod_headers.c>
Header set X-UA-Compatible "IE=edge"
# `mod_headers` can't match based on the content-type, however, we only
# want to send this header for HTML pages and not for the other resources
<FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
Header unset X-UA-Compatible
</FilesMatch>
</IfModule>
# ------------------------------------------------------------------------------
# | Cookie setting from iframes |
# ------------------------------------------------------------------------------
# Allow cookies to be set from iframes in IE.
# <IfModule mod_headers.c>
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
# </IfModule>
# ------------------------------------------------------------------------------
# | Screen flicker |
# ------------------------------------------------------------------------------
# Stop screen flicker in IE on CSS rollovers (this only works in
# combination with the `ExpiresByType` directives for images from below).
# BrowserMatch "MSIE" brokenvary=1
# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
# BrowserMatch "Opera" !brokenvary
# SetEnvIf brokenvary 1 force-no-vary
# ##############################################################################
# # MIME TYPES AND ENCODING #
# ##############################################################################
# ------------------------------------------------------------------------------
# | Proper MIME types for all files |
# ------------------------------------------------------------------------------
<IfModule mod_mime.c>
# Audio
AddType audio/mp4 m4a f4a f4b
AddType audio/ogg oga ogg
# JavaScript
# Normalize to standard type (it's sniffed in IE anyways):
# http://tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js jsonp
AddType application/json json
# Video
AddType video/mp4 mp4 m4v f4v f4p
AddType video/ogg ogv
AddType video/webm webm
AddType video/x-flv flv
# Web fonts
AddType application/font-woff woff
AddType application/vnd.ms-fontobject eot
# Browsers usually ignore the font MIME types and sniff the content,
# however, Chrome shows a warning if other MIME types are used for the
# following fonts.
AddType application/x-font-ttf ttc ttf
AddType font/opentype otf
# Make SVGZ fonts work on iPad:
# https://twitter.com/FontSquirrel/status/14855840545
AddType image/svg+xml svg svgz
AddEncoding gzip svgz
# Other
AddType application/octet-stream safariextz
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-shockwave-flash swf
AddType application/x-web-app-manifest+json webapp
AddType application/x-xpinstall xpi
AddType application/xml atom rdf rss xml
AddType image/webp webp
AddType image/x-icon ico
AddType text/cache-manifest appcache manifest
AddType text/vtt vtt
AddType text/x-component htc
AddType text/x-vcard vcf
</IfModule>
# ------------------------------------------------------------------------------
# | UTF-8 encoding |
# ------------------------------------------------------------------------------
# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
AddDefaultCharset utf-8
# Force UTF-8 for certain file formats.
<IfModule mod_mime.c>
AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
</IfModule>
# ##############################################################################
# # URL REWRITES #
# ##############################################################################
# ------------------------------------------------------------------------------
# | Rewrite engine |
# ------------------------------------------------------------------------------
# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
# necessary for the following directives to work.
# If your web host doesn't allow the `FollowSymlinks` option, you may need to
# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
# Also, some cloud hosting services require `RewriteBase` to be set:
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
<IfModule mod_rewrite.c>
Options +FollowSymlinks
# Options +SymLinksIfOwnerMatch
RewriteEngine On
# RewriteBase /
</IfModule>
# ------------------------------------------------------------------------------
# | Suppressing / Forcing the "www." at the beginning of URLs |
# ------------------------------------------------------------------------------
# The same content should never be available under two different URLs especially
# not with and without "www." at the beginning. This can cause SEO problems
# (duplicate content), therefore, you should choose one of the alternatives and
# redirect the other one.
# By default option 1 (no "www.") is activated:
# http://no-www.org/faq.php?q=class_b
# If you'd prefer to use option 2, just comment out all the lines from option 1
# and uncomment the ones from option 2.
# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Option 1: rewrite www.example.com → example.com
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Option 2: rewrite example.com → www.example.com
# Be aware that the following might not be a good idea if you use "real"
# subdomains for certain parts of your website.
# <IfModule mod_rewrite.c>
# RewriteCond %{HTTPS} !=on
# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# </IfModule>
# ##############################################################################
# # SECURITY #
# ##############################################################################
# ------------------------------------------------------------------------------
# | Content Security Policy (CSP) |
# ------------------------------------------------------------------------------
# You can mitigate the risk of cross-site scripting and other content-injection
# attacks by setting a Content Security Policy which whitelists trusted sources
# of content for your site.
# The example header below allows ONLY scripts that are loaded from the current
# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
# work as-is for your site!
# To get all the details you'll need to craft a reasonable policy for your site,
# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
# see the specification: http://w3.org/TR/CSP).
# <IfModule mod_headers.c>
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
# <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$">
# Header unset Content-Security-Policy
# </FilesMatch>
# </IfModule>
# ------------------------------------------------------------------------------
# | File access |
# ------------------------------------------------------------------------------
# Block access to directories without a default document.
# Usually you should leave this uncommented because you shouldn't allow anyone
# to surf through every directory on your server (which may includes rather
# private places like the CMS's directories).
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to hidden files and directories.
# This includes directories used by version control systems such as Git and SVN.
<IfModule mod_rewrite.c>
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to backup and source files.
# These files may be left by some text editors and can pose a great security
# danger when anyone has access to them.
<FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
# ------------------------------------------------------------------------------
# | Secure Sockets Layer (SSL) |
# ------------------------------------------------------------------------------
# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
# prevent `https://www.example.com` when your certificate only allows
# `https://secure.example.com`.
# <IfModule mod_rewrite.c>
# RewriteCond %{SERVER_PORT} !^443
# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
# </IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Force client-side SSL redirection.
# If a user types "example.com" in his browser, the above rule will redirect him
# to the secure version of the site. That still leaves a window of opportunity
# (the initial HTTP connection) for an attacker to downgrade or redirect the
# request. The following header ensures that browser will ONLY connect to your
# server via HTTPS, regardless of what the users type in the address bar.
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
# <IfModule mod_headers.c>
# Header set Strict-Transport-Security max-age=16070400;
# </IfModule>
# ------------------------------------------------------------------------------
# | Server software information |
# ------------------------------------------------------------------------------
# Avoid displaying the exact Apache version number, the description of the
# generic OS-type and the information about Apache's compiled-in modules.
# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
# ServerTokens Prod
# ##############################################################################
# # WEB PERFORMANCE #
# ##############################################################################
# ------------------------------------------------------------------------------
# | Compression |
# ------------------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force compression for mangled headers.
# http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# Compress all output labeled with one of the following MIME-types
# (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
# and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
# as `AddOutputFilterByType` is still in the core directives).
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE application/atom+xml \
application/javascript \
application/json \
application/rss+xml \
application/vnd.ms-fontobject \
application/x-font-ttf \
application/x-web-app-manifest+json \
application/xhtml+xml \
application/xml \
font/opentype \
image/svg+xml \
image/x-icon \
text/css \
text/html \
text/plain \
text/x-component \
text/xml
</IfModule>
</IfModule>
# ------------------------------------------------------------------------------
# | Content transformations |
# ------------------------------------------------------------------------------
# Prevent some of the mobile network providers from modifying the content of
# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
# <IfModule mod_headers.c>
# Header set Cache-Control "no-transform"
# </IfModule>
# ------------------------------------------------------------------------------
# | ETag removal |
# ------------------------------------------------------------------------------
# Since we're sending far-future expires headers (see below), ETags can
# be removed: http://developer.yahoo.com/performance/rules.html#etags.
# `FileETag None` is not enough for every server.
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None
# ------------------------------------------------------------------------------
# | Expires headers (for better cache control) |
# ------------------------------------------------------------------------------
# The following expires headers are set pretty far in the future. If you don't
# control versioning with filename-based cache busting, consider lowering the
# cache time for resources like CSS and JS to something like 1 week.
<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 1 month"
# CSS
ExpiresByType text/css "access plus 1 year"
# Data interchange
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
# Favicon (cannot be renamed!)
ExpiresByType image/x-icon "access plus 1 week"
# HTML components (HTCs)
ExpiresByType text/x-component "access plus 1 month"
# HTML
ExpiresByType text/html "access plus 0 seconds"
# JavaScript
ExpiresByType application/javascript "access plus 1 year"
# Manifest files
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
ExpiresByType text/cache-manifest "access plus 0 seconds"
# Media
ExpiresByType audio/ogg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 month"
ExpiresByType video/ogg "access plus 1 month"
ExpiresByType video/webm "access plus 1 month"
# Web feeds
ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"
# Web fonts
ExpiresByType application/font-woff "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
ExpiresByType application/x-font-ttf "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
</IfModule>
# ------------------------------------------------------------------------------
# | Filename-based cache busting |
# ------------------------------------------------------------------------------
# If you're not using a build process to manage your filename version revving,
# you might want to consider enabling the following directives to route all
# requests such as `/css/style.12345.css` to `/css/style.css`.
# To understand why this is important and a better idea than `*.css?v231`, read:
# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
# <IfModule mod_rewrite.c>
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
# </IfModule>
# ------------------------------------------------------------------------------
# | File concatenation |
# ------------------------------------------------------------------------------
# Allow concatenation from within specific CSS and JS files, e.g.:
# Inside of `script.combined.js` you could have
# <!--#include file="libs/jquery.js" -->
# <!--#include file="plugins/jquery.idletimer.js" -->
# and they would be included into this single file.
# <IfModule mod_include.c>
# <FilesMatch "\.combined\.js$">
# Options +Includes
# AddOutputFilterByType INCLUDES application/javascript application/json
# SetOutputFilter INCLUDES
# </FilesMatch>
# <FilesMatch "\.combined\.css$">
# Options +Includes
# AddOutputFilterByType INCLUDES text/css
# SetOutputFilter INCLUDES
# </FilesMatch>
# </IfModule>
# ------------------------------------------------------------------------------
# | Persistent connections |
# ------------------------------------------------------------------------------
# Allow multiple requests to be sent over the same TCP connection:
# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
# Enable if you serve a lot of static content but, be aware of the
# possible disadvantages!
# <IfModule mod_headers.c>
# Header set Connection Keep-Alive
# </IfModule>
================================================
FILE: client/.jshintrc
================================================
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"ignoreDelimiters": [
{ "start": "start-non-standard", "end": "end-non-standard" }
],
"globals": {
"jQuery": true,
"angular": true,
"console": true,
"$": true,
"_": true,
"moment": true,
"jasmine": true,
"describe": true,
"beforeEach": true,
"module": true,
"inject": true,
"it": true,
"expect": true,
"browser": true,
"element": true,
"by": true
}
}
================================================
FILE: client/app/account/account.js
================================================
'use strict';
angular.module('paizaqaApp')
.config(function($stateProvider) {
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'app/account/login/login.html',
controller: 'LoginController',
controllerAs: 'vm'
})
.state('logout', {
url: '/logout?referrer',
referrer: 'main',
template: '',
controller: function($state, Auth) {
var referrer = $state.params.referrer ||
$state.current.referrer ||
'main';
Auth.logout();
$state.go(referrer);
}
})
.state('signup', {
url: '/signup',
templateUrl: 'app/account/signup/signup.html',
controller: 'SignupController',
controllerAs: 'vm'
})
.state('settings', {
url: '/settings',
templateUrl: 'app/account/settings/settings.html',
controller: 'SettingsController',
controllerAs: 'vm',
authenticate: true
});
})
.run(function($rootScope) {
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, current) {
if (next.name === 'logout' && current && current.name && !current.authenticate) {
next.referrer = current.name;
}
});
});
================================================
FILE: client/app/account/login/login.controller.js
================================================
'use strict';
class LoginController {
constructor(Auth, $state) {
this.user = {};
this.errors = {};
this.submitted = false;
this.Auth = Auth;
this.$state = $state;
}
login(form) {
this.submitted = true;
if (form.$valid) {
this.Auth.login({
email: this.user.email,
password: this.user.password
})
.then(() => {
// Logged in, redirect to home
this.$state.go('main');
})
.catch(err => {
this.errors.other = err.message;
});
}
}
}
angular.module('paizaqaApp')
.controller('LoginController', LoginController);
================================================
FILE: client/app/account/login/login.html
================================================
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Login</h1>
<p>Accounts are reset on server restart from <code>server/config/seed.js</code>. Default account is <code>test@example.com</code> / <code>test</code></p>
<p>Admin account is <code>admin@example.com</code> / <code>admin</code></p>
</div>
<div class="col-sm-12">
<form class="form" name="form" ng-submit="vm.login(form)" novalidate>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" ng-model="vm.user.email" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control" ng-model="vm.user.password" required>
</div>
<div class="form-group has-error">
<p class="help-block" ng-show="form.email.$error.required && form.password.$error.required && vm.submitted">
Please enter your email and password.
</p>
<p class="help-block" ng-show="form.email.$error.email && vm.submitted">
Please enter a valid email.
</p>
<p class="help-block">{{ vm.errors.other }}</p>
</div>
<div>
<button class="btn btn-inverse btn-lg btn-login" type="submit">
Login
</button>
<a class="btn btn-default btn-lg btn-register" ui-sref="signup">
Register
</a>
</div>
<hr/>
<div class="row">
<div class="col-sm-4 col-md-3">
<oauth-buttons classes="btn-block"></oauth-buttons>
</div>
</div>
</form>
</div>
</div>
<hr>
</div>
================================================
FILE: client/app/account/settings/settings.controller.js
================================================
'use strict';
class SettingsController {
constructor(Auth) {
this.errors = {};
this.submitted = false;
this.Auth = Auth;
}
changePassword(form) {
this.submitted = true;
if (form.$valid) {
this.Auth.changePassword(this.user.oldPassword, this.user.newPassword)
.then(() => {
this.message = 'Password successfully changed.';
})
.catch(() => {
form.password.$setValidity('mongoose', false);
this.errors.other = 'Incorrect password';
this.message = '';
});
}
}
}
angular.module('paizaqaApp')
.controller('SettingsController', SettingsController);
================================================
FILE: client/app/account/settings/settings.html
================================================
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Change Password</h1>
</div>
<div class="col-sm-12">
<form class="form" name="form" ng-submit="vm.changePassword(form)" novalidate>
<div class="form-group">
<label>Current Password</label>
<input type="password" name="password" class="form-control" ng-model="vm.user.oldPassword"
mongoose-error/>
<p class="help-block" ng-show="form.password.$error.mongoose">
{{ vm.errors.other }}
</p>
</div>
<div class="form-group">
<label>New Password</label>
<input type="password" name="newPassword" class="form-control" ng-model="vm.user.newPassword"
ng-minlength="3"
required/>
<p class="help-block"
ng-show="(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || vm.submitted)">
Password must be at least 3 characters.
</p>
</div>
<div class="form-group">
<label>Confirm New Password</label>
<input type="password" name="confirmPassword" class="form-control" ng-model="vm.user.confirmPassword"
match="vm.user.newPassword"
ng-minlength="3"
required=""/>
<p class="help-block"
ng-show="form.confirmPassword.$error.match && vm.submitted">
Passwords must match.
</p>
</div>
<p class="help-block"> {{ vm.message }} </p>
<button class="btn btn-lg btn-primary" type="submit">Save changes</button>
</form>
</div>
</div>
</div>
================================================
FILE: client/app/account/signup/signup.controller.js
================================================
'use strict';
class SignupController {
//start-non-standard
user = {};
errors = {};
submitted = false;
//end-non-standard
constructor(Auth, $state) {
this.Auth = Auth;
this.$state = $state;
}
register(form) {
this.submitted = true;
if (form.$valid) {
this.Auth.createUser({
name: this.user.name,
email: this.user.email,
password: this.user.password
})
.then(() => {
// Account created, redirect to home
this.$state.go('main');
})
.catch(err => {
err = err.data;
this.errors = {};
// Update validity of form fields that match the mongoose errors
angular.forEach(err.errors, (error, field) => {
form[field].$setValidity('mongoose', false);
this.errors[field] = error.message;
});
});
}
}
}
angular.module('paizaqaApp')
.controller('SignupController', SignupController);
================================================
FILE: client/app/account/signup/signup.html
================================================
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Sign up</h1>
</div>
<div class="col-sm-12">
<form class="form" name="form" ng-submit="vm.register(form)" novalidate>
<div class="form-group" ng-class="{ 'has-success': form.name.$valid && vm.submitted,
'has-error': form.name.$invalid && vm.submitted }">
<label>Name</label>
<input type="text" name="name" class="form-control" ng-model="vm.user.name"
required/>
<p class="help-block" ng-show="form.name.$error.required && vm.submitted">
A name is required
</p>
</div>
<div class="form-group" ng-class="{ 'has-success': form.email.$valid && vm.submitted,
'has-error': form.email.$invalid && vm.submitted }">
<label>Email</label>
<input type="email" name="email" class="form-control" ng-model="vm.user.email"
required
mongoose-error/>
<p class="help-block" ng-show="form.email.$error.email && vm.submitted">
Doesn't look like a valid email.
</p>
<p class="help-block" ng-show="form.email.$error.required && vm.submitted">
What's your email address?
</p>
<p class="help-block" ng-show="form.email.$error.mongoose">
{{ vm.errors.email }}
</p>
</div>
<div class="form-group" ng-class="{ 'has-success': form.password.$valid && vm.submitted,
'has-error': form.password.$invalid && vm.submitted }">
<label>Password</label>
<input type="password" name="password" class="form-control" ng-model="vm.user.password"
ng-minlength="3"
required
mongoose-error/>
<p class="help-block"
ng-show="(form.password.$error.minlength || form.password.$error.required) && vm.submitted">
Password must be at least 3 characters.
</p>
<p class="help-block" ng-show="form.password.$error.mongoose">
{{ vm.errors.password }}
</p>
</div>
<div class="form-group" ng-class="{ 'has-success': form.confirmPassword.$valid && vm.submitted,
'has-error': form.confirmPassword.$invalid && vm.submitted }">
<label>Confirm Password</label>
<input type="password" name="confirmPassword" class="form-control" ng-model="vm.user.confirmPassword"
match="vm.user.password"
ng-minlength="3" required/>
<p class="help-block"
ng-show="form.confirmPassword.$error.match && vm.submitted">
Passwords must match.
</p>
</div>
<div>
<button class="btn btn-inverse btn-lg btn-register" type="submit">
Sign up
</button>
<a class="btn btn-default btn-lg btn-login" ui-sref="login">
Login
</a>
</div>
<hr/>
<div class="row">
<div class="col-sm-4 col-md-3">
<oauth-buttons classes="btn-block"></oauth-buttons>
</div>
</div>
</form>
</div>
</div>
<hr>
</div>
================================================
FILE: client/app/admin/admin.controller.js
================================================
'use strict';
(function() {
class AdminController {
constructor(User) {
// Use the User $resource to fetch all users
this.users = User.query();
}
delete(user) {
user.$remove();
this.users.splice(this.users.indexOf(user), 1);
}
}
angular.module('paizaqaApp.admin')
.controller('AdminController', AdminController);
})();
================================================
FILE: client/app/admin/admin.html
================================================
<div class="container">
<p>The delete user and user index api routes are restricted to users with the 'admin' role.</p>
<ul class="list-group user-list">
<li class="list-group-item" ng-repeat="user in admin.users">
<div class="user-info">
<strong>{{user.name}}</strong><br>
<span class="text-muted">{{user.email}}</span>
</div>
<a ng-click="admin.delete(user)" class="trash"><span class="fa fa-trash fa-2x"></span></a>
</li>
</ul>
</div>
================================================
FILE: client/app/admin/admin.module.js
================================================
'use strict';
angular.module('paizaqaApp.admin', [
'paizaqaApp.auth',
'ui.router'
]);
================================================
FILE: client/app/admin/admin.router.js
================================================
'use strict';
angular.module('paizaqaApp.admin')
.config(function($stateProvider) {
$stateProvider
.state('admin', {
url: '/admin',
templateUrl: 'app/admin/admin.html',
controller: 'AdminController',
controllerAs: 'admin',
authenticate: 'admin'
});
});
================================================
FILE: client/app/admin/admin.scss
================================================
.trash { color:rgb(209, 91, 71); }
.user-list {
li {
display: flex;
border: none;
border-bottom: 1px lightgray solid;
margin-bottom: 0;
&:last-child { border-bottom: none; }
.user-info {
flex-grow: 1;
}
.trash {
display: flex;
align-items: center;
text-decoration: none;
}
}
}
================================================
FILE: client/app/app.constant.js
================================================
(function(angular, undefined) {
'use strict';
angular.module('paizaqaApp.constants', [])
.constant('appConfig', {userRoles:['guest','user','admin']})
;
})(angular);
================================================
FILE: client/app/app.js
================================================
'use strict';
angular.module('paizaqaApp', [
'paizaqaApp.auth',
'paizaqaApp.admin',
'paizaqaApp.constants',
'ngCookies',
'ngResource',
'ngSanitize',
'btford.socket-io',
'ui.router',
'ui.bootstrap',
'validation.match',
'ui.pagedown',
'ngTagsInput',
'ngMessages',
'infinite-scroll',
])
.config(function($urlRouterProvider, $locationProvider) {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
});
================================================
FILE: client/app/app.scss
================================================
$icon-font-path: "../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/";
@import '../bower_components/bootstrap-sass-official/vendor/assets/stylesheets/bootstrap';
@import '../bower_components/bootstrap-social/bootstrap-social.scss';
$fa-font-path: "../bower_components/font-awesome/fonts";
@import '../bower_components/font-awesome/scss/font-awesome';
/**
* App-wide Styles
*/
.browserupgrade {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
// Component styles are injected through grunt
// injector
@import 'admin/admin.scss';
@import 'questionsCreate/questionsCreate.scss';
@import 'questionsIndex/questionsIndex.scss';
@import 'questionsShow/questionsShow.scss';
@import '../components/footer/footer.scss';
@import '../components/modal/modal.scss';
@import '../components/oauth-buttons/oauth-buttons.scss';
// endinjector
================================================
FILE: client/app/fromNow/fromNow.filter.js
================================================
'use strict';
angular.module('paizaqaApp')
.filter('fromNow', function () {
return function (input) {
return moment(input).locale(window.navigator.language).fromNow();
};
});
================================================
FILE: client/app/fromNow/fromNow.filter.spec.js
================================================
'use strict';
describe('Filter: fromNow', function () {
// load the filter's module
beforeEach(module('paizaqaApp'));
// initialize a new instance of the filter before each test
var fromNow;
beforeEach(inject(function ($filter) {
fromNow = $filter('fromNow');
}));
it('return "a few seconds ago" for now', function () {
expect(fromNow(Date.now())).toBe('a few seconds ago');
});
});
================================================
FILE: client/app/questionsCreate/questionsCreate.controller.js
================================================
'use strict';
angular.module('paizaqaApp')
.controller('QuestionsCreateCtrl', function ($scope, $http, $location, Auth) {
if(! Auth.isLoggedIn()){
$location.path('/login');
$location.replace();
return;
}
$scope.submit = function() {
$http.post('/api/questions', $scope.question).success(function(){
$location.path('/');
});
};
});
================================================
FILE: client/app/questionsCreate/questionsCreate.controller.spec.js
================================================
'use strict';
describe('Controller: QuestionsCreateCtrl', function () {
// load the controller's module
beforeEach(module('paizaqaApp'));
var QuestionsCreateCtrl, scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
QuestionsCreateCtrl = $controller('QuestionsCreateCtrl', {
$scope: scope
});
}));
it('should ...', function () {
expect(1).toEqual(1);
});
});
================================================
FILE: client/app/questionsCreate/questionsCreate.html
================================================
<div class="container">
<form name="form" ng-submit="submit()">
<h2>Title:</h2>
<input type="text" class="form-control" ng-model="question.title" name="question_title" required>
<span class="text-danger" ng-messages="form.question_title.$error">
<span ng-message="required">Required</span>
</span>
<span class="text-success" ng-show="form.question_title.$valid">OK</span>
<br>
<h2>Question:</h2>
<pagedown-editor ng-model="question.content" name="question_content" required></pagedown-editor>
<span class="text-danger" ng-messages="form.question_content.$error">
<span ng-message="required">Required</span>
</span>
<span class="text-success" ng-show="form.question_content.$valid">OK</span>
<h2>Tags:</h2>
<tags-input ng-model="question.tags">
<!-- <auto-complete source="loadTags($query)"></auto-complete> -->
</tags-input>
<input type="submit" class="btn btn-primary" ng-disabled="form.$invalid" value="Post question">
</form>
</div>
================================================
FILE: client/app/questionsCreate/questionsCreate.js
================================================
'use strict';
angular.module('paizaqaApp')
.config(function ($stateProvider) {
$stateProvider
.state('questionsCreate', {
url: '/questions/create',
templateUrl: 'app/questionsCreate/questionsCreate.html',
controller: 'QuestionsCreateCtrl'
});
});
================================================
FILE: client/app/questionsCreate/questionsCreate.scss
================================================
================================================
FILE: client/app/questionsIndex/questionsIndex.controller.js
================================================
'use strict';
angular.module('paizaqaApp')
.controller('QuestionsIndexCtrl', function ($scope, $http, $location, Auth, query) {
var keyword = $location.search().keyword;
if(keyword){
query = _.merge(query, {$text: {$search: keyword}});
}
$scope.busy = true;
$scope.noMoreData = false;
$http.get('/api/questions', {params: {query: query}}).success(function(questions) {
$scope.questions = questions;
if($scope.questions.length < 20){
$scope.noMoreData = true;
}
$scope.busy = false;
});
$scope.nextPage = function(){
if($scope.busy){ return; }
$scope.busy = true;
var lastId = $scope.questions[$scope.questions.length-1]._id;
var pageQuery = _.merge(query, {_id: {$lt: lastId}});
$http.get('/api/questions', {params: {query: pageQuery}}).success(function(questions){
$scope.questions = $scope.questions.concat(questions);
$scope.busy = false;
if(questions.length === 0){
$scope.noMoreData = true;
}
});
};
$scope.isStar = function(obj){
return Auth.isLoggedIn() && obj && obj.stars && obj.stars.indexOf(Auth.getCurrentUser()._id)!==-1;
};
});
================================================
FILE: client/app/questionsIndex/questionsIndex.controller.spec.js
================================================
'use strict';
describe('Controller: QuestionsIndexCtrl', function () {
// load the controller's module
beforeEach(module('paizaqaApp'));
var QuestionsIndexCtrl, scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
QuestionsIndexCtrl = $controller('QuestionsIndexCtrl', {
$scope: scope,
query: {},
});
}));
it('should ...', function () {
expect(1).toEqual(1);
});
});
================================================
FILE: client/app/questionsIndex/questionsIndex.html
================================================
<header class="hero-unit" id="banner">
<div class="container">
<h1>paizaQA</h1>
<p class="lead">How to make QA sites like this: <a style="color: #c0c0c0;" href="http://engineering.paiza.io/entry/2016/03/10/115345">Building a QA web service in an hour - MEAN stack development(3)</a></p>
<img src="assets/images/yeoman.png" alt="I'm Yeoman">
</div>
</header>
<div class="container" infinite-scroll='nextPage()' infinite-scroll-disabled='busy || noMoreData'>
<br/>
<div style="text-align: center">
<a type="button" class="btn btn-primary" href="/questions/create">Ask Question</a>
</div>
<table class="table table-striped">
<thead>
<tr>
<th width="20">Stars</th>
<th width="20">Answers</th>
<th>Question</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="question in questions">
<td style="text-align: center; vertical-align:middle">
<div style="font-size: xx-large;">{{question.stars.length}}</div>
</td>
<td style="text-align: center; vertical-align:middle">
<div style="font-size: xx-large;">{{question.answers.length}}</div>
</td>
<td>
<div style="float: right;">
<span ng-if=" isStar(question)" class="glyphicon glyphicon-star" style="color: #CF7C00;" ></span>
<span ng-if="!isStar(question)" class="glyphicon glyphicon-star-empty"></span>
</div>
<a ng-href="/questions/show/{{question._id}}" style="font-size: large">{{question.title}}</a>
<div class="clearfix"></div>
<div style="float: right;">
by <a ng-href="/users/{{question.user._id}}">{{question.user.name}}</a>
- {{question.createdAt|fromNow}}
</div>
<div>
<span ng-repeat="tag in question.tags">
<span class="label label-info">
{{tag.text}}
</span>
</span>
</div>
<div class="clearfix"></div>
</td>
</tr>
</tbody>
</table>
<div ng-show='busy'>Loading data...</div>
</div>
================================================
FILE: client/app/questionsIndex/questionsIndex.js
================================================
'use strict';
angular.module('paizaqaApp')
.config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/?keyword',
templateUrl: 'app/questionsIndex/questionsIndex.html',
controller: 'QuestionsIndexCtrl',
resolve: {
query: function(){return {};}
},
})
.state('starredQuestionsIndex', {
url: '/users/:userId/starred',
templateUrl: 'app/questionsIndex/questionsIndex.html',
controller: 'QuestionsIndexCtrl',
resolve: {
query: function($stateParams){
return {
$or: [
{'stars': $stateParams.userId},
{'answers.stars': $stateParams.userId},
{'comments.stars': $stateParams.userId},
{'answers.comments.stars': $stateParams.userId},
]
};
}
},
})
.state('userQuestionsIndex', {
url: '/users/:userId',
templateUrl: 'app/questionsIndex/questionsIndex.html',
controller: 'QuestionsIndexCtrl',
resolve: {
query: function($stateParams){
return {user: $stateParams.userId};
}
},
});
});
================================================
FILE: client/app/questionsIndex/questionsIndex.scss
================================================
#banner {
border-bottom: none;
margin-top: -20px;
}
#banner h1 {
font-size: 60px;
line-height: 1;
letter-spacing: -1px;
}
.hero-unit {
position: relative;
padding: 30px 15px;
color: #F5F5F5;
text-align: center;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
background: #4393B9;
}
================================================
FILE: client/app/questionsShow/questionsShow.controller.js
================================================
'use strict';
angular.module('paizaqaApp')
.controller('QuestionsShowCtrl', function ($scope, $http, $stateParams, Auth, $location) {
var loadQuestions = function(){
$http.get('/api/questions/' + $stateParams.id).success(function(question) {
$scope.question = question;
});
};
loadQuestions();
$scope.newAnswer = {};
$scope.submitAnswer = function() {
$http.post('/api/questions/' + $stateParams.id + '/answers', $scope.newAnswer).success(function(){
loadQuestions();
$scope.newAnswer = {};
});
};
$scope.deleteQuestion = function() {
$http.delete('/api/questions/' + $stateParams.id).success(function(){
$location.path('/');
});
};
$scope.deleteAnswer = function(answer) {
$http.delete('/api/questions/' + $stateParams.id + '/answers/' + answer._id).success(function(){
loadQuestions();
});
};
$scope.updateQuestion = function() {
$http.put('/api/questions/' + $stateParams.id, $scope.question).success(function(){
loadQuestions();
});
};
$scope.updateAnswer = function(answer) {
$http.put('/api/questions/' + $stateParams.id + '/answers/' + answer._id, answer).success(function(){
loadQuestions();
});
};
$scope.isOwner = function(obj){
return Auth.isLoggedIn() && obj && obj.user && obj.user._id === Auth.getCurrentUser()._id;
};
$scope.newComment = {};
$scope.submitComment = function() {
$http.post('/api/questions/' + $stateParams.id + '/comments', $scope.newComment).success(function(){
loadQuestions();
$scope.newComment = {};
$scope.editNewComment = false;
});
};
$scope.submitAnswerComment = function(answer) {
$http.post('/api/questions/' + $stateParams.id + '/answers/' + answer._id + '/comments', answer.newAnswerComment).success(function(){
loadQuestions();
});
};
$scope.deleteComment = function(comment) {
$http.delete('/api/questions/' + $stateParams.id + '/comments/' + comment._id).success(function(){
loadQuestions();
});
};
$scope.deleteAnswerComment = function(answer, answerComment) {
$http.delete('/api/questions/' + $stateParams.id + '/answers/' + answer._id + '/comments/' + answerComment._id).success(function(){
loadQuestions();
});
};
$scope.updateComment = function(comment) {
$http.put('/api/questions/' + $stateParams.id + '/comments/' + comment._id, comment).success(function(){
loadQuestions();
});
};
$scope.updateAnswerComment = function(answer, answerComment) {
$http.put('/api/questions/' + $stateParams.id + '/answers/' + answer._id + '/comments/' + answerComment._id, answerComment).success(function(){
loadQuestions();
});
};
$scope.isStar = function(obj){
return Auth.isLoggedIn() && obj && obj.stars && obj.stars.indexOf(Auth.getCurrentUser()._id)!==-1;
};
$scope.star = function(subpath) {
$http.put('/api/questions/' + $scope.question._id + subpath + '/star').success(function(){
loadQuestions();
});
};
$scope.unstar = function(subpath) {
$http.delete('/api/questions/' + $scope.question._id + subpath + '/star').success(function(){
loadQuestions();
});
};
});
================================================
FILE: client/app/questionsShow/questionsShow.controller.spec.js
================================================
'use strict';
describe('Controller: QuestionsShowCtrl', function () {
// load the controller's module
beforeEach(module('paizaqaApp'));
var QuestionsShowCtrl, scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
QuestionsShowCtrl = $controller('QuestionsShowCtrl', {
$scope: scope
});
}));
it('should ...', function () {
expect(1).toEqual(1);
});
});
================================================
FILE: client/app/questionsShow/questionsShow.html
================================================
<div class="container" id="question-show-container">
<div>
<button ng-if="isOwner(question)" type="button" class="close" ng-click="deleteQuestion()">×</button>
<div style="float: left;font-size: x-large; padding: 0; width: 2em; text-align: center;">
<button ng-if=" isStar(question)" type="button" style="background: transparent; border: 0;" ng-click="unstar('')">
<span class="glyphicon glyphicon-star" style="color: #CF7C00;" ></span>
</button>
<button ng-if="!isStar(question)" type="button" style="background: transparent; border: 0;" ng-click="star('')" >
<span class="glyphicon glyphicon-star-empty"></span>
</button>
<br/>
<div>{{question.stars.length}}</div>
</div>
<div>
<h1>
<div ng-if="! editting">{{question.title}}</div>
<input type=text ng-model="question.title" ng-if=" editting">
</h1>
<span ng-repeat="tag in question.tags">
<span class="label label-info">
{{tag.text}}
</span>
</span>
</div>
</div>
<hr/>
<pagedown-viewer content="question.content" ng-if="!editting"></pagedown-viewer>
<pagedown-editor ng-model="question.content" ng-if=" editting"></pagedown-editor>
<button type="submit" class="btn btn-primary" ng-click="editting=false;updateQuestion()" ng-show=" editting">Save</button>
<a ng-click="editting=!editting;" ng-show="isOwner(question) && !editting">Edit</a>
<div class="text-right">by <a ng-href="/users/{{question.user._id}}">{{question.user.name}}</a> - {{question.createdAt|fromNow}}</div>
<div class="comment">
<div ng-repeat="comment in question.comments">
<hr/>
<button ng-if="isOwner(comment)" type="button" class="close" ng-click="deleteComment(comment)">×</button>
<div style="float: left;font-size: normal; padding: 0; width: 2em; text-align: center;">
<button ng-if=" isStar(comment)" type="button" style="background: transparent; border: 0;" ng-click="unstar('/comments/' + comment._id)">
<span class="glyphicon glyphicon-star" style="color: #CF7C00;" ></span>
</button>
<button ng-if="!isStar(comment)" type="button" style="background: transparent; border: 0;" ng-click=" star('/comments/' + comment._id)" >
<span class="glyphicon glyphicon-star-empty"></span>
</button>
<br/>
<div>{{comment.stars.length}}</div>
</div>
<pagedown-viewer content="comment.content" ng-if="!editting"></pagedown-viewer>
<pagedown-editor ng-model="comment.content" ng-if=" editting"></pagedown-editor>
<button type="submit" class="btn btn-primary" ng-click="editting=false;updateComment(comment)" ng-show=" editting">Save</button>
<a ng-click="editting=!editting;" ng-show="isOwner(comment) && !editting">Edit</a>
<div class="text-right" style="vertical-align: bottom;">by <a ng-href="/users/{{comment.user._id}}">{{comment.user.name}}</a> - {{comment.createdAt|fromNow}}</div>
<div class="clearfix"></div>
</div>
<hr/>
<a ng-click="editNewComment=!editNewComment;">add a comment</a>
<form ng-if="editNewComment" name="commentForm">
<pagedown-editor ng-model="newComment.content" editor-class="'comment-wmd-input'"
name="commentEditor" required>
</pagedown-editor>
<button type="button" class="btn btn-primary" ng-click="submitComment()" ng-disabled="commentForm.$invalid">Add Comment</button>
</form>
</div>
<h3>{{question.answers.length}} Answers</h3>
<div ng-repeat="answer in question.answers">
<hr/>
<div style="float: left;font-size: large; padding: 0; width: 2em; text-align: center;">
<button ng-if=" isStar(answer)" type="button" style="background: transparent; border: 0;" ng-click="unstar('/answers/' + answer._id)">
<span class="glyphicon glyphicon-star" style="color: #CF7C00;" ></span>
</button>
<button ng-if="!isStar(answer)" type="button" style="background: transparent; border: 0;" ng-click=" star('/answers/' + answer._id)" >
<span class="glyphicon glyphicon-star-empty"></span>
</button>
<br/>
<div>{{answer.stars.length}}</div>
</div>
<div class="answer">
<button ng-if="isOwner(answer)" type="button" class="close" ng-click="deleteAnswer(answer)">×</button>
<pagedown-viewer content="answer.content" ng-if="!editting"></pagedown-viewer>
<pagedown-editor ng-model="answer.content" ng-if=" editting"></pagedown-editor>
<button type="submit" class="btn btn-primary" ng-click="editting=false;updateAnswer(answer)" ng-show=" editting">Save</button>
<a ng-click="editting=!editting;" ng-show="isOwner(answer) && !editting">Edit</a>
</div>
<div class="text-right">by {{answer.user.name}} - {{answer.createdAt|fromNow}}</div>
<div class="comment">
<div ng-repeat="comment in answer.comments">
<hr/>
<button ng-if="isOwner(comment)" type="button" class="close" ng-click="deleteAnswerComment(answer, comment)">×</button>
<div style="float: left;font-size: normal; padding: 0; width: 2em; text-align: center;">
<button ng-if=" isStar(comment)" type="button" style="background: transparent; border: 0;" ng-click="unstar('/answers/' + answer._id + '/comments/' + comment._id)">
<span class="glyphicon glyphicon-star" style="color: #CF7C00;" ></span>
</button>
<button ng-if="!isStar(comment)" type="button" style="background: transparent; border: 0;" ng-click=" star('/answers/' + answer._id + '/comments/' + comment._id)" >
<span class="glyphicon glyphicon-star-empty"></span>
</button>
<br/>
<div>{{comment.stars.length}}</div>
</div>
<pagedown-viewer content="comment.content" ng-if="!editting"></pagedown-viewer>
<pagedown-editor ng-model="comment.content" ng-if=" editting"></pagedown-editor>
<button type="submit" class="btn btn-primary" ng-click="editting=false;updateAnswerComment(answer, comment)" ng-show=" editting">Save</button>
<a ng-click="editting=!editting;" ng-show="isOwner(comment) && !editting">Edit</a>
<div class="text-right">by <a ng-href="/users/{{question.user._id}}">{{comment.user.name}}</a> - {{comment.createdAt|fromNow}}</div>
<div class="clearfix"></div>
</div>
<hr/>
<a ng-click="editNewAnswerComment=!editNewAnswerComment;answer.newAnswerComment={}">add a comment</a>
<form ng-if="editNewAnswerComment" name="answer_{{answer.id}}_comment">
<hr/>
<pagedown-editor ng-model="answer.newAnswerComment.content" editor-class="'comment-wmd-input'"
required>
</pagedown-editor>
<button type="button" class="btn btn-primary" ng-click="submitAnswerComment(answer)" ng-disabled="answer_{{answer.id}}_comment.$invalid">Add Comment</button>
</form>
</div>
</div>
<hr/>
<h3>Your answer</h3>
<form name="answerForm" ng-submit="submitAnswer()">
<pagedown-editor ng-model="newAnswer.content" name="answerEditor" required></pagedown-editor>
<input type="submit" class="btn btn-primary" ng-disabled="answerForm.$invalid" value="Submit your answer">
</form>
</div>
================================================
FILE: client/app/questionsShow/questionsShow.js
================================================
'use strict';
angular.module('paizaqaApp')
.config(function ($stateProvider) {
$stateProvider
.state('questionsShow', {
url: '/questions/show/:id',
templateUrl: 'app/questionsShow/questionsShow.html',
controller: 'QuestionsShowCtrl'
});
});
================================================
FILE: client/app/questionsShow/questionsShow.scss
================================================
#question-show-container .comment{
hr {
margin: 0;
};
p {
margin: 0;
};
margin-left: 100px;
};
#question-show-container .answer{
margin-left: 50px;
};
.comment-wmd-input {
height: 50px;
width: 100%;
background-color: Gainsboro;
border: 1px solid DarkGray;
};
================================================
FILE: client/components/auth/auth.module.js
================================================
'use strict';
angular.module('paizaqaApp.auth', [
'paizaqaApp.constants',
'paizaqaApp.util',
'ngCookies',
'ui.router'
])
.config(function($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
});
================================================
FILE: client/components/auth/auth.service.js
================================================
'use strict';
(function() {
function AuthService($location, $http, $cookies, $q, appConfig, Util, User) {
var safeCb = Util.safeCb;
var currentUser = {};
var userRoles = appConfig.userRoles || [];
if ($cookies.get('token') && $location.path() !== '/logout') {
currentUser = User.get();
}
var Auth = {
/**
* Authenticate user and save token
*
* @param {Object} user - login info
* @param {Function} callback - optional, function(error, user)
* @return {Promise}
*/
login({email, password}, callback) {
return $http.post('/auth/local', {
email: email,
password: password
})
.then(res => {
$cookies.put('token', res.data.token);
currentUser = User.get();
return currentUser.$promise;
})
.then(user => {
safeCb(callback)(null, user);
return user;
})
.catch(err => {
Auth.logout();
safeCb(callback)(err.data);
return $q.reject(err.data);
});
},
/**
* Delete access token and user info
*/
logout() {
$cookies.remove('token');
currentUser = {};
},
/**
* Create a new user
*
* @param {Object} user - user info
* @param {Function} callback - optional, function(error, user)
* @return {Promise}
*/
createUser(user, callback) {
return User.save(user,
function(data) {
$cookies.put('token', data.token);
currentUser = User.get();
return safeCb(callback)(null, user);
},
function(err) {
Auth.logout();
return safeCb(callback)(err);
}).$promise;
},
/**
* Change password
*
* @param {String} oldPassword
* @param {String} newPassword
* @param {Function} callback - optional, function(error, user)
* @return {Promise}
*/
changePassword(oldPassword, newPassword, callback) {
return User.changePassword({ id: currentUser._id }, {
oldPassword: oldPassword,
newPassword: newPassword
}, function() {
return safeCb(callback)(null);
}, function(err) {
return safeCb(callback)(err);
}).$promise;
},
/**
* Gets all available info on a user
* (synchronous|asynchronous)
*
* @param {Function|*} callback - optional, funciton(user)
* @return {Object|Promise}
*/
getCurrentUser(callback) {
if (arguments.length === 0) {
return currentUser;
}
var value = (currentUser.hasOwnProperty('$promise')) ?
currentUser.$promise : currentUser;
return $q.when(value)
.then(user => {
safeCb(callback)(user);
return user;
}, () => {
safeCb(callback)({});
return {};
});
},
/**
* Check if a user is logged in
* (synchronous|asynchronous)
*
* @param {Function|*} callback - optional, function(is)
* @return {Bool|Promise}
*/
isLoggedIn(callback) {
if (arguments.length === 0) {
return currentUser.hasOwnProperty('role');
}
return Auth.getCurrentUser(null)
.then(user => {
var is = user.hasOwnProperty('role');
safeCb(callback)(is);
return is;
});
},
/**
* Check if a user has a specified role or higher
* (synchronous|asynchronous)
*
* @param {String} role - the role to check against
* @param {Function|*} callback - optional, function(has)
* @return {Bool|Promise}
*/
hasRole(role, callback) {
var hasRole = function(r, h) {
return userRoles.indexOf(r) >= userRoles.indexOf(h);
};
if (arguments.length < 2) {
return hasRole(currentUser.role, role);
}
return Auth.getCurrentUser(null)
.then(user => {
var has = (user.hasOwnProperty('role')) ?
hasRole(user.role, role) : false;
safeCb(callback)(has);
return has;
});
},
/**
* Check if a user is an admin
* (synchronous|asynchronous)
*
* @param {Function|*} callback - optional, function(is)
* @return {Bool|Promise}
*/
isAdmin() {
return Auth.hasRole
.apply(Auth, [].concat.apply(['admin'], arguments));
},
/**
* Get auth token
*
* @return {String} - a token string used for authenticating
*/
getToken() {
return $cookies.get('token');
}
};
return Auth;
}
angular.module('paizaqaApp.auth')
.factory('Auth', AuthService);
})();
================================================
FILE: client/components/auth/interceptor.service.js
================================================
'use strict';
(function() {
function authInterceptor($rootScope, $q, $cookies, $injector, Util) {
var state;
return {
// Add authorization token to headers
request(config) {
config.headers = config.headers || {};
if ($cookies.get('token') && Util.isSameOrigin(config.url)) {
config.headers.Authorization = 'Bearer ' + $cookies.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError(response) {
if (response.status === 401) {
(state || (state = $injector.get('$state'))).go('login');
// remove any stale tokens
$cookies.remove('token');
}
return $q.reject(response);
}
};
}
angular.module('paizaqaApp.auth')
.factory('authInterceptor', authInterceptor);
})();
================================================
FILE: client/components/auth/router.decorator.js
================================================
'use strict';
(function() {
angular.module('paizaqaApp.auth')
.run(function($rootScope, $state, Auth) {
// Redirect to login if route requires auth and the user is not logged in, or doesn't have required role
$rootScope.$on('$stateChangeStart', function(event, next) {
if (!next.authenticate) {
return;
}
if (typeof next.authenticate === 'string') {
Auth.hasRole(next.authenticate, _.noop).then(has => {
if (has) {
return;
}
event.preventDefault();
return Auth.isLoggedIn(_.noop).then(is => {
$state.go(is ? 'main' : 'login');
});
});
} else {
Auth.isLoggedIn(_.noop).then(is => {
if (is) {
return;
}
event.preventDefault();
$state.go('main');
});
}
});
});
})();
================================================
FILE: client/components/auth/user.service.js
================================================
'use strict';
(function() {
function UserResource($resource) {
return $resource('/api/users/:id/:controller', {
id: '@_id'
}, {
changePassword: {
method: 'PUT',
params: {
controller: 'password'
}
},
get: {
method: 'GET',
params: {
id: 'me'
}
}
});
}
angular.module('paizaqaApp.auth')
.factory('User', UserResource);
})();
================================================
FILE: client/components/footer/footer.directive.js
================================================
'use strict';
angular.module('paizaqaApp')
.directive('footer', function() {
return {
templateUrl: 'components/footer/footer.html',
restrict: 'E',
link: function(scope, element) {
element.addClass('footer');
}
};
});
================================================
FILE: client/components/footer/footer.html
================================================
<div class="container">
<p>Angular Fullstack v3.3.0 |
<a href="https://twitter.com/tyhenkel">@tyhenkel</a> |
<a href="https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open">Issues</a>
</p>
</div>
================================================
FILE: client/components/footer/footer.scss
================================================
footer.footer {
text-align: center;
padding: 30px 0;
margin-top: 70px;
border-top: 1px solid #E5E5E5;
}
================================================
FILE: client/components/modal/modal.html
================================================
<div class="modal-header">
<button ng-if="modal.dismissable" type="button" ng-click="$dismiss()" class="close">×</button>
<h4 ng-if="modal.title" ng-bind="modal.title" class="modal-title"></h4>
</div>
<div class="modal-body">
<p ng-if="modal.text" ng-bind="modal.text"></p>
<div ng-if="modal.html" ng-bind-html="modal.html"></div>
</div>
<div class="modal-footer">
<button ng-repeat="button in modal.buttons" ng-class="button.classes" ng-click="button.click($event)" ng-bind="button.text" class="btn"></button>
</div>
================================================
FILE: client/components/modal/modal.scss
================================================
.modal-primary,
.modal-info,
.modal-success,
.modal-warning,
.modal-danger {
.modal-header {
color: #fff;
border-radius: 5px 5px 0 0;
}
}
.modal-primary .modal-header {
background: $brand-primary;
}
.modal-info .modal-header {
background: $brand-info;
}
.modal-success .modal-header {
background: $brand-success;
}
.modal-warning .modal-header {
background: $brand-warning;
}
.modal-danger .modal-header {
background: $brand-danger;
}
================================================
FILE: client/components/modal/modal.service.js
================================================
'use strict';
angular.module('paizaqaApp')
.factory('Modal', function($rootScope, $modal) {
/**
* Opens a modal
* @param {Object} scope - an object to be merged with modal's scope
* @param {String} modalClass - (optional) class(es) to be applied to the modal
* @return {Object} - the instance $modal.open() returns
*/
function openModal(scope = {}, modalClass = 'modal-default') {
var modalScope = $rootScope.$new();
angular.extend(modalScope, scope);
return $modal.open({
templateUrl: 'components/modal/modal.html',
windowClass: modalClass,
scope: modalScope
});
}
// Public API here
return {
/* Confirmation modals */
confirm: {
/**
* Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)')
* @param {Function} del - callback, ran when delete is confirmed
* @return {Function} - the function to open the modal (ex. myModalFn)
*/
delete(del = angular.noop) {
/**
* Open a delete confirmation modal
* @param {String} name - name or info to show on modal
* @param {All} - any additional args are passed straight to del callback
*/
return function() {
var args = Array.prototype.slice.call(arguments),
name = args.shift(),
deleteModal;
deleteModal = openModal({
modal: {
dismissable: true,
title: 'Confirm Delete',
html: '<p>Are you sure you want to delete <strong>' + name + '</strong> ?</p>',
buttons: [{
classes: 'btn-danger',
text: 'Delete',
click: function(e) {
deleteModal.close(e);
}
}, {
classes: 'btn-default',
text: 'Cancel',
click: function(e) {
deleteModal.dismiss(e);
}
}]
}
}, 'modal-danger');
deleteModal.result.then(function(event) {
del.apply(event, args);
});
};
}
}
};
});
================================================
FILE: client/components/mongoose-error/mongoose-error.directive.js
================================================
'use strict';
/**
* Removes server error when user updates input
*/
angular.module('paizaqaApp')
.directive('mongooseError', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
element.on('keydown', () => ngModel.$setValidity('mongoose', true));
}
};
});
================================================
FILE: client/components/navbar/navbar.controller.js
================================================
'use strict';
class NavbarController {
//start-non-standard
isCollapsed = true;
//end-non-standard
constructor(Auth, $state) {
this.menu = [
{
'title': 'All',
'link': function(){return '/';},
'show': function(){return true;},
},
{
'title': 'Mine',
'link': function(){return '/users/' + Auth.getCurrentUser()._id;},
'show': Auth.isLoggedIn,
},
{
'title': 'Starred',
'link': function(){return '/users/' + Auth.getCurrentUser()._id + '/starred';},
'show': Auth.isLoggedIn,
},
];
this.isLoggedIn = Auth.isLoggedIn;
this.isAdmin = Auth.isAdmin;
this.getCurrentUser = Auth.getCurrentUser;
this.search = function(keyword) {
$state.go('main', {keyword: keyword}, {reload: true});
};
}
}
angular.module('paizaqaApp')
.controller('NavbarController', NavbarController);
================================================
FILE: client/components/navbar/navbar.directive.js
================================================
'use strict';
angular.module('paizaqaApp')
.directive('navbar', () => ({
templateUrl: 'components/navbar/navbar.html',
restrict: 'E',
controller: 'NavbarController',
controllerAs: 'nav'
}));
================================================
FILE: client/components/navbar/navbar.html
================================================
<div class="navbar navbar-default navbar-static-top" ng-controller="NavbarController">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" type="button" ng-click="nav.isCollapsed = !nav.isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/" class="navbar-brand">paizaqa</a>
</div>
<div collapse="nav.isCollapsed" class="navbar-collapse collapse" id="navbar-main">
<ul class="nav navbar-nav">
<li ng-repeat="item in nav.menu" ng-class="{active: isActive(item.link())}" ng-show="item.show()">
<a ng-href="{{item.link()}}">{{item.title}}</a>
</li>
</ul>
<form class="navbar-form navbar-left" role="search" ng-submit="nav.search(keyword)">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" ng-model="keyword">
<span class="input-group-btn">
<button type="submit" class="btn btn-default">
<span class="glyphicon glyphicon-search">
</span>
</button>
</span>
</div>
</form>
<ul class="nav navbar-nav navbar-right">
<li ng-hide="nav.isLoggedIn()" ui-sref-active="active"><a ui-sref="signup">Sign up</a></li>
<li ng-hide="nav.isLoggedIn()" ui-sref-active="active"><a ui-sref="login">Login</a></li>
<li ng-show="nav.isLoggedIn()"><p class="navbar-text">Hello {{ nav.getCurrentUser().name }}</p> </li>
<li ng-show="nav.isLoggedIn()" ui-sref-active="active"><a ui-sref="settings"><span class="glyphicon glyphicon-cog"></span></a></li>
<li ng-show="nav.isLoggedIn()"><a ui-sref="logout">Logout</a></li>
</ul>
</div>
</div>
</div>
================================================
FILE: client/components/oauth-buttons/oauth-buttons.controller.js
================================================
'use strict';
angular.module('paizaqaApp')
.controller('OauthButtonsCtrl', function($window) {
this.loginOauth = function(provider) {
$window.location.href = '/auth/' + provider;
};
});
================================================
FILE: client/components/oauth-buttons/oauth-buttons.controller.spec.js
================================================
'use strict';
describe('Controller: OauthButtonsCtrl', function() {
// load the controller's module
beforeEach(module('paizaqaApp'));
var OauthButtonsCtrl, $window;
// Initialize the controller and a mock $window
beforeEach(inject(function($controller) {
$window = {
location: {}
};
OauthButtonsCtrl = $controller('OauthButtonsCtrl', {
$window: $window
});
}));
it('should attach loginOauth', function() {
expect(OauthButtonsCtrl.loginOauth).toEqual(jasmine.any(Function));
});
});
================================================
FILE: client/components/oauth-buttons/oauth-buttons.directive.js
================================================
'use strict';
angular.module('paizaqaApp')
.directive('oauthButtons', function() {
return {
templateUrl: 'components/oauth-buttons/oauth-buttons.html',
restrict: 'EA',
controller: 'OauthButtonsCtrl',
controllerAs: 'OauthButtons',
scope: {
classes: '@'
}
};
});
================================================
FILE: client/components/oauth-buttons/oauth-buttons.directive.spec.js
================================================
'use strict';
describe('Directive: oauthButtons', function() {
// load the directive's module and view
beforeEach(module('paizaqaApp'));
beforeEach(module('components/oauth-buttons/oauth-buttons.html'));
var element, parentScope, elementScope;
var compileDirective = function(template) {
inject(function($compile) {
element = angular.element(template);
element = $compile(element)(parentScope);
parentScope.$digest();
elementScope = element.isolateScope();
});
};
beforeEach(inject(function($rootScope) {
parentScope = $rootScope.$new();
}));
it('should contain anchor buttons', function() {
compileDirective('<oauth-buttons></oauth-buttons>');
expect(element.find('a.btn.btn-social').length).toBeGreaterThan(0);
});
it('should evaluate and bind the classes attribute to scope.classes', function() {
parentScope.scopedClass = 'scopedClass1';
compileDirective('<oauth-buttons classes="testClass1 {{scopedClass}}"></oauth-buttons>');
expect(elementScope.classes).toEqual('testClass1 scopedClass1');
});
it('should bind scope.classes to class names on the anchor buttons', function() {
compileDirective('<oauth-buttons></oauth-buttons>');
// Add classes
elementScope.classes = 'testClass1 testClass2';
elementScope.$digest();
expect(element.find('a.btn.btn-social.testClass1.testClass2').length).toBeGreaterThan(0);
// Remove classes
elementScope.classes = '';
elementScope.$digest();
expect(element.find('a.btn.btn-social.testClass1.testClass2').length).toEqual(0);
});
});
================================================
FILE: client/components/oauth-buttons/oauth-buttons.html
================================================
<a ng-class="classes" ng-click="OauthButtons.loginOauth('facebook')" class="btn btn-social btn-facebook">
<i class="fa fa-facebook"></i>
Connect with Facebook
</a>
<a ng-class="classes" ng-click="OauthButtons.loginOauth('google')" class="btn btn-social btn-google">
<i class="fa fa-google-plus"></i>
Connect with Google+
</a>
<a ng-class="classes" ng-click="OauthButtons.loginOauth('twitter')" class="btn btn-social btn-twitter">
<i class="fa fa-twitter"></i>
Connect with Twitter
</a>
================================================
FILE: client/components/oauth-buttons/oauth-buttons.scss
================================================
================================================
FILE: client/components/socket/socket.mock.js
================================================
'use strict';
angular.module('socketMock', [])
.factory('socket', function() {
return {
socket: {
connect: function() {},
on: function() {},
emit: function() {},
receive: function() {}
},
syncUpdates: function() {},
unsyncUpdates: function() {}
};
});
================================================
FILE: client/components/socket/socket.service.js
================================================
/* global io */
'use strict';
angular.module('paizaqaApp')
.factory('socket', function(socketFactory) {
// socket.io now auto-configures its connection when we ommit a connection url
var ioSocket = io('', {
// Send auth token on connection, you will need to DI the Auth service above
// 'query': 'token=' + Auth.getToken()
path: '/socket.io-client'
});
var socket = socketFactory({ ioSocket });
return {
socket,
/**
* Register listeners to sync an array with updates on a model
*
* Takes the array we want to sync, the model name that socket updates are sent from,
* and an optional callback function after new items are updated.
*
* @param {String} modelName
* @param {Array} array
* @param {Function} cb
*/
syncUpdates(modelName, array, cb) {
cb = cb || angular.noop;
/**
* Syncs item creation/updates on 'model:save'
*/
socket.on(modelName + ':save', function (item) {
var oldItem = _.find(array, {_id: item._id});
var index = array.indexOf(oldItem);
var event = 'created';
// replace oldItem if it exists
// otherwise just add item to the collection
if (oldItem) {
array.splice(index, 1, item);
event = 'updated';
} else {
array.push(item);
}
cb(event, item, array);
});
/**
* Syncs removed items on 'model:remove'
*/
socket.on(modelName + ':remove', function (item) {
var event = 'deleted';
_.remove(array, {_id: item._id});
cb(event, item, array);
});
},
/**
* Removes listeners for a models updates on the socket
*
* @param modelName
*/
unsyncUpdates(modelName) {
socket.removeAllListeners(modelName + ':save');
socket.removeAllListeners(modelName + ':remove');
}
};
});
================================================
FILE: client/components/ui-router/ui-router.mock.js
================================================
'use strict';
angular.module('stateMock', []);
angular.module('stateMock').service('$state', function($q) {
this.expectedTransitions = [];
this.transitionTo = function(stateName) {
if (this.expectedTransitions.length > 0) {
var expectedState = this.expectedTransitions.shift();
if (expectedState !== stateName) {
throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName);
}
} else {
throw Error('No more transitions were expected! Tried to transition to ' + stateName);
}
console.log('Mock transition to: ' + stateName);
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve();
return promise;
};
this.go = this.transitionTo;
this.expectTransitionTo = function(stateName) {
this.expectedTransitions.push(stateName);
};
this.ensureAllTransitionsHappened = function() {
if (this.expectedTransitions.length > 0) {
throw Error('Not all transitions happened!');
}
};
});
================================================
FILE: client/components/util/util.module.js
================================================
'use strict';
angular.module('paizaqaApp.util', []);
================================================
FILE: client/components/util/util.service.js
================================================
'use strict';
(function() {
/**
* The Util service is for thin, globally reusable, utility functions
*/
function UtilService($window) {
var Util = {
/**
* Return a callback or noop function
*
* @param {Function|*} cb - a 'potential' function
* @return {Function}
*/
safeCb(cb) {
return (angular.isFunction(cb)) ? cb : angular.noop;
},
/**
* Parse a given url with the use of an anchor element
*
* @param {String} url - the url to parse
* @return {Object} - the parsed url, anchor element
*/
urlParse(url) {
var a = document.createElement('a');
a.href = url;
// Special treatment for IE, see http://stackoverflow.com/a/13405933 for details
if (a.host === '') {
a.href = a.href;
}
return a;
},
/**
* Test whether or not a given url is same origin
*
* @param {String} url - url to test
* @param {String|String[]} [origins] - additional origins to test against
* @return {Boolean} - true if url is same origin
*/
isSameOrigin(url, origins) {
url = Util.urlParse(url);
origins = (origins && [].concat(origins)) || [];
origins = origins.map(Util.urlParse);
origins.push($window.location);
origins = origins.filter(function(o) {
return url.hostname === o.hostname &&
url.port === o.port &&
url.protocol === o.protocol;
});
return (origins.length >= 1);
}
};
return Util;
}
angular.module('paizaqaApp.util')
.factory('Util', UtilService);
})();
================================================
FILE: client/index.html
================================================
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<base href="/">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(client) app/vendor.css -->
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/angular-pagedown/angular-pagedown.css" />
<link rel="stylesheet" href="bower_components/ng-tags-input/ng-tags-input.min.css" />
<!-- endbower -->
<!-- endbuild -->
<!-- build:css({.tmp,client}) app/app.css -->
<link rel="stylesheet" href="app/app.css">
<!-- injector:css -->
<!-- endinjector -->
<!-- endbuild -->
</head>
<body ng-app="paizaqaApp">
<!--[if lt IE 7]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<!-- Add your site or application content here -->
<navbar></navbar>
<div ui-view=""></div>
<footer></footer>
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-X');
ga('send', 'pageview');
</script>
<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/json3/lib/json3.min.js"></script>
<![endif]-->
<!-- build:js({client,node_modules}) app/vendor.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="bower_components/lodash/dist/lodash.compat.js"></script>
<script src="bower_components/angular-socket-io/socket.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<script src="bower_components/angular-validation-match/dist/angular-validation-match.min.js"></script>
<script src="bower_components/pagedown/Markdown.Converter.js"></script>
<script src="bower_components/pagedown/Markdown.Sanitizer.js"></script>
<script src="bower_components/pagedown/Markdown.Extra.js"></script>
<script src="bower_components/pagedown/Markdown.Editor.js"></script>
<script src="bower_components/angular-pagedown/angular-pagedown.js"></script>
<script src="bower_components/ng-tags-input/ng-tags-input.min.js"></script>
<script src="bower_components/angular-messages/angular-messages.js"></script>
<script src="bower_components/moment/moment.js"></script>
<script src="bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js"></script>
<!-- endbower -->
<script src="bower_components/moment/min/moment-with-locales.min.js"></script>
<script src="socket.io-client/socket.io.js"></script>
<!-- endbuild -->
<!-- build:js(.tmp) app/app.js -->
<script src="app/app.js"></script>
<!-- injector:js -->
<script src="components/auth/auth.module.js"></script>
<script src="app/admin/admin.module.js"></script>
<script src="components/util/util.module.js"></script>
<script src="app/account/signup/signup.controller.js"></script>
<script src="app/admin/admin.controller.js"></script>
<script src="app/account/account.js"></script>
<script src="app/admin/admin.router.js"></script>
<script src="app/app.constant.js"></script>
<script src="app/fromNow/fromNow.filter.js"></script>
<script src="app/questionsCreate/questionsCreate.controller.js"></script>
<script src="app/questionsCreate/questionsCreate.js"></script>
<script src="app/questionsIndex/questionsIndex.controller.js"></script>
<script src="app/questionsIndex/questionsIndex.js"></script>
<script src="app/questionsShow/questionsShow.controller.js"></script>
<script src="app/questionsShow/questionsShow.js"></script>
<script src="app/account/login/login.controller.js"></script>
<script src="components/auth/auth.service.js"></script>
<script src="components/auth/interceptor.service.js"></script>
<script src="components/auth/router.decorator.js"></script>
<script src="components/auth/user.service.js"></script>
<script src="components/footer/footer.directive.js"></script>
<script src="components/modal/modal.service.js"></script>
<script src="components/mongoose-error/mongoose-error.directive.js"></script>
<script src="components/navbar/navbar.controller.js"></script>
<script src="components/navbar/navbar.directive.js"></script>
<script src="components/oauth-buttons/oauth-buttons.controller.js"></script>
<script src="components/oauth-buttons/oauth-buttons.directive.js"></script>
<script src="components/socket/socket.service.js"></script>
<script src="app/account/settings/settings.controller.js"></script>
<script src="components/util/util.service.js"></script>
<!-- endinjector -->
<!-- endbuild -->
<a href="https://github.com/gi-no/paizaqa"><img style="position: absolute; top: 50px; right: 0; border: 0;" src="https://camo.githubusercontent.com/e7bbb0521b397edbd5fe43e7f760759336b5e05f/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677265656e5f3030373230302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png"></a>
</body>
</html>
================================================
FILE: client/robots.txt
================================================
# robotstxt.org
User-agent: *
================================================
FILE: e2e/account/login/login.po.js
================================================
/**
* This file uses the Page Object pattern to define the main page for tests
* https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
*/
'use strict';
var LoginPage = function() {
var form = this.form = element(by.css('.form'));
form.email = form.element(by.model('vm.user.email'));
form.password = form.element(by.model('vm.user.password'));
form.submit = form.element(by.css('.btn-login'));
form.oauthButtons = require('../../components/oauth-buttons/oauth-buttons.po').oauthButtons;
this.login = function(data) {
for (var prop in data) {
var formElem = form[prop];
if (data.hasOwnProperty(prop) && formElem && typeof formElem.sendKeys === 'function') {
formElem.sendKeys(data[prop]);
}
}
return form.submit.click();
};
};
module.exports = new LoginPage();
================================================
FILE: e2e/account/login/login.spec.js
================================================
'use strict';
var config = browser.params;
var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');
describe('Login View', function() {
var page;
var loadPage = function() {
browser.get(config.baseUrl + '/login');
page = require('./login.po');
};
var testUser = {
name: 'Test User',
email: 'test@example.com',
password: 'test'
};
beforeEach(function(done) {
UserModel.removeAsync()
.then(function() {
return UserModel.createAsync(testUser);
})
.then(loadPage)
.finally(function() {
browser.wait(function() {
//console.log('waiting for angular...');
return browser.executeScript('return !!window.angular');
}, 5000).then(done);
});
});
it('should include login form with correct inputs and submit button', function() {
expect(page.form.email.getAttribute('type')).toBe('email');
expect(page.form.email.getAttribute('name')).toBe('email');
expect(page.form.password.getAttribute('type')).toBe('password');
expect(page.form.password.getAttribute('name')).toBe('password');
expect(page.form.submit.getAttribute('type')).toBe('submit');
expect(page.form.submit.getText()).toBe('Login');
});
it('should include oauth buttons with correct classes applied', function() {
expect(page.form.oauthButtons.facebook.getText()).toBe('Connect with Facebook');
expect(page.form.oauthButtons.facebook.getAttribute('class')).toMatch('btn-block');
expect(page.form.oauthButtons.google.getText()).toBe('Connect with Google+');
expect(page.form.oauthButtons.google.getAttribute('class')).toMatch('btn-block');
expect(page.form.oauthButtons.twitter.getText()).toBe('Connect with Twitter');
expect(page.form.oauthButtons.twitter.getAttribute('class')).toMatch('btn-block');
});
describe('with local auth', function() {
it('should login a user and redirecting to "/"', function() {
page.login(testUser);
var navbar = require('../../components/navbar/navbar.po');
expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
});
it('should indicate login failures', function() {
page.login({
email: testUser.email,
password: 'badPassword'
});
expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/login');
var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
expect(helpBlock.getText()).toBe('This password is not correct.');
});
});
});
================================================
FILE: e2e/account/logout/logout.spec.js
================================================
'use strict';
var config = browser.params;
var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');
describe('Logout View', function() {
var login = function(user) {
browser.get(config.baseUrl + '/login');
require('../login/login.po').login(user);
};
var testUser = {
name: 'Test User',
email: 'test@example.com',
password: 'test'
};
beforeEach(function(done) {
UserModel.removeAsync()
.then(function() {
return UserModel.createAsync(testUser);
})
.then(function() {
return login(testUser);
})
.finally(function() {
browser.wait(function() {
return browser.executeScript('return !!window.angular');
}, 5000).then(done);
});
});
describe('with local auth', function() {
it('should logout a user and redirecting to "/"', function() {
var navbar = require('../../components/navbar/navbar.po');
expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
browser.get(config.baseUrl + '/logout');
navbar = require('../../components/navbar/navbar.po');
expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
expect(navbar.navbarAccountGreeting.isDisplayed()).toBe(false);
});
});
});
================================================
FILE: e2e/account/signup/signup.po.js
================================================
/**
* This file uses the Page Object pattern to define the main page for tests
* https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
*/
'use strict';
var SignupPage = function() {
var form = this.form = element(by.css('.form'));
form.name = form.element(by.model('vm.user.name'));
form.email = form.element(by.model('vm.user.email'));
form.password = form.element(by.model('vm.user.password'));
form.confirmPassword = form.element(by.model('vm.user.confirmPassword'));
form.submit = form.element(by.css('.btn-register'));
form.oauthButtons = require('../../components/oauth-buttons/oauth-buttons.po').oauthButtons;
this.signup = function(data) {
for (var prop in data) {
var formElem = form[prop];
if (data.hasOwnProperty(prop) && formElem && typeof formElem.sendKeys === 'function') {
formElem.sendKeys(data[prop]);
}
}
return form.submit.click();
};
};
module.exports = new SignupPage();
================================================
FILE: e2e/account/signup/signup.spec.js
================================================
'use strict';
var config = browser.params;
var UserModel = require(config.serverConfig.root + '/server/api/user/user.model');
describe('Signup View', function() {
var page;
var loadPage = function() {
browser.manage().deleteAllCookies();
browser.get(config.baseUrl + '/signup');
page = require('./signup.po');
};
var testUser = {
name: 'Test',
email: 'test@example.com',
password: 'test',
confirmPassword: 'test'
};
beforeEach(function(done) {
loadPage();
browser.wait(function() {
return browser.executeScript('return !!window.angular');
}, 5000).then(done);
});
it('should include signup form with correct inputs and submit button', function() {
expect(page.form.name.getAttribute('type')).toBe('text');
expect(page.form.name.getAttribute('name')).toBe('name');
expect(page.form.email.getAttribute('type')).toBe('email');
expect(page.form.email.getAttribute('name')).toBe('email');
expect(page.form.password.getAttribute('type')).toBe('password');
expect(page.form.password.getAttribute('name')).toBe('password');
expect(page.form.confirmPassword.getAttribute('type')).toBe('password');
expect(page.form.confirmPassword.getAttribute('name')).toBe('confirmPassword');
expect(page.form.submit.getAttribute('type')).toBe('submit');
expect(page.form.submit.getText()).toBe('Sign up');
});
it('should include oauth buttons with correct classes applied', function() {
expect(page.form.oauthButtons.facebook.getText()).toBe('Connect with Facebook');
expect(page.form.oauthButtons.facebook.getAttribute('class')).toMatch('btn-block');
expect(page.form.oauthButtons.google.getText()).toBe('Connect with Google+');
expect(page.form.oauthButtons.google.getAttribute('class')).toMatch('btn-block');
expect(page.form.oauthButtons.twitter.getText()).toBe('Connect with Twitter');
expect(page.form.oauthButtons.twitter.getAttribute('class')).toMatch('btn-block');
});
describe('with local auth', function() {
beforeAll(function(done) {
UserModel.removeAsync().then(done);
});
it('should signup a new user, log them in, and redirecting to "/"', function() {
page.signup(testUser);
var navbar = require('../../components/navbar/navbar.po');
expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/');
expect(navbar.navbarAccountGreeting.getText()).toBe('Hello ' + testUser.name);
});
it('should indicate signup failures', function() {
page.signup(testUser);
expect(browser.getCurrentUrl()).toBe(config.baseUrl + '/signup');
expect(page.form.email.getAttribute('class')).toContain('ng-invalid-mongoose');
var helpBlock = page.form.element(by.css('.form-group.has-error .help-block.ng-binding'));
expect(helpBlock.getText()).toBe('The specified email address is already in use.');
});
});
});
================================================
FILE: e2e/components/navbar/navbar.po.js
================================================
/**
* This file uses the Page Object pattern to define the main page for tests
* https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
*/
'use strict';
var NavbarComponent = function() {
this.navbar = element(by.css('.navbar'));
this.navbarHeader = this.navbar.element(by.css('.navbar-header'));
this.navbarNav = this.navbar.element(by.css('#navbar-main .nav.navbar-nav:not(.navbar-right)'));
this.navbarAccount = this.navbar.element(by.css('#navbar-main .nav.navbar-nav.navbar-right'));
this.navbarAccountGreeting = this.navbarAccount.element(by.binding('getCurrentUser().name'));
};
module.exports = new NavbarComponent();
================================================
FILE: e2e/components/oauth-buttons/oauth-buttons.po.js
================================================
/**
* This file uses the Page Object pattern to define the main page for tests
* https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
*/
'use strict';
var OauthButtons = function() {
var oauthButtons = this.oauthButtons = element(by.css('oauth-buttons'));
oauthButtons.facebook = oauthButtons.element(by.css('.btn.btn-social.btn-facebook'));
oauthButtons.google = oauthButtons.element(by.css('.btn.btn-social.btn-google'));
oauthButtons.twitter = oauthButtons.element(by.css('.btn.btn-social.btn-twitter'));
};
module.exports = new OauthButtons();
================================================
FILE: e2e/main/main.po.js
================================================
/**
* This file uses the Page Object pattern to define the main page for tests
* https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
*/
'use strict';
var MainPage = function() {
this.heroEl = element(by.css('.hero-unit'));
this.h1El = this.heroEl.element(by.css('h1'));
this.imgEl = this.heroEl.element(by.css('img'));
};
module.exports = new MainPage();
================================================
FILE: e2e/main/main.spec.js
================================================
'use strict';
var config = browser.params;
describe('Main View', function() {
var page;
beforeEach(function() {
browser.get(config.baseUrl + '/');
page = require('./main.po');
});
it('should include jumbotron with correct data', function() {
expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
expect(page.imgEl.getAttribute('src')).toMatch(/yeoman.png$/);
expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
});
});
================================================
FILE: karma.conf.js
================================================
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
// bower:js
'client/bower_components/jquery/dist/jquery.js',
'client/bower_components/angular/angular.js',
'client/bower_components/angular-resource/angular-resource.js',
'client/bower_components/angular-cookies/angular-cookies.js',
'client/bower_components/angular-sanitize/angular-sanitize.js',
'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'client/bower_components/lodash/dist/lodash.compat.js',
'client/bower_components/angular-socket-io/socket.js',
'client/bower_components/angular-ui-router/release/angular-ui-router.js',
'client/bower_components/angular-validation-match/dist/angular-validation-match.min.js',
'client/bower_components/pagedown/Markdown.Converter.js',
'client/bower_components/pagedown/Markdown.Sanitizer.js',
'client/bower_components/pagedown/Markdown.Extra.js',
'client/bower_components/pagedown/Markdown.Editor.js',
'client/bower_components/angular-pagedown/angular-pagedown.js',
'client/bower_components/ng-tags-input/ng-tags-input.min.js',
'client/bower_components/angular-messages/angular-messages.js',
'client/bower_components/moment/moment.js',
'client/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
'client/bower_components/angular-mocks/angular-mocks.js',
// endbower
'node_modules/socket.io-client/socket.io.js',
'client/app/app.js',
'client/{app,components}/**/*.module.js',
'client/{app,components}/**/*.js',
'client/{app,components}/**/*.html'
],
preprocessors: {
'**/*.html': 'ng-html2js',
'client/{app,components}/**/*.js': 'babel'
},
ngHtml2JsPreprocessor: {
stripPrefix: 'client/'
},
babelPreprocessor: {
options: {
sourceMap: 'inline',
optional: [
'es7.classProperties'
]
},
filename: function (file) {
return file.originalPath.replace(/\.js$/, '.es5.js');
},
sourceFileName: function (file) {
return file.originalPath;
}
},
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// reporter types:
// - dots
// - progress (default)
// - spec (karma-spec-reporter)
// - junit
// - growl
// - coverage
reporters: ['spec'],
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
});
};
================================================
FILE: mocha.conf.js
================================================
'use strict';
// Register the Babel require hook
require('babel-core/register');
var chai = require('chai');
// Load Chai assertions
global.expect = chai.expect;
global.assert = chai.assert;
chai.should();
// Load Sinon
global.sinon = require('sinon');
// Initialize Chai plugins
chai.use(require('sinon-chai'));
chai.use(require('chai-as-promised'));
chai.use(require('chai-things'))
================================================
FILE: package.json
================================================
{
"name": "paizaqa",
"version": "0.0.0",
"main": "server/app.js",
"dependencies": {
"babel-runtime": "^5.8.20",
"bluebird": "^2.9.34",
"body-parser": "^1.13.3",
"composable-middleware": "^0.3.0",
"compression": "^1.5.2",
"connect-mongo": "^0.8.1",
"cookie-parser": "^1.3.5",
"ejs": "^2.3.3",
"errorhandler": "^1.4.2",
"express": "^4.13.3",
"express-jwt": "^3.0.0",
"express-session": "^1.11.3",
"jsonwebtoken": "^5.0.0",
"lodash": "^3.10.1",
"lusca": "^1.3.0",
"method-override": "^2.3.5",
"mongoose": "^4.1.2",
"morgan": "~1.6.1",
"passport": "~0.3.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth": "~0.2.0",
"passport-local": "^1.0.0",
"passport-twitter": "^1.0.3",
"serve-favicon": "^2.3.0",
"socket.io": "^1.3.5",
"socket.io-client": "^1.3.5",
"socketio-jwt": "^4.2.0",
"tiny-segmenter": "r7kamura/tiny-segmenter"
},
"devDependencies": {
"autoprefixer": "^6.0.0",
"babel-core": "^5.6.4",
"grunt": "~0.4.5",
"grunt-wiredep": "^2.0.0",
"grunt-concurrent": "^2.0.1",
"grunt-contrib-clean": "~0.7.0",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-cssmin": "~0.14.0",
"grunt-contrib-imagemin": "~1.0.0",
"grunt-contrib-jshint": "~0.11.2",
"grunt-contrib-uglify": "~0.11.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-babel": "~5.0.0",
"grunt-google-cdn": "~0.4.0",
"grunt-jscs": "^2.1.0",
"grunt-newer": "^1.1.1",
"grunt-ng-annotate": "^1.0.1",
"grunt-ng-constant": "^1.1.0",
"grunt-filerev": "^2.3.1",
"grunt-usemin": "^3.0.0",
"grunt-env": "~0.4.1",
"grunt-node-inspector": "^0.4.1",
"grunt-nodemon": "^0.4.0",
"grunt-angular-templates": "^0.5.4",
"grunt-dom-munger": "^3.4.0",
"grunt-protractor-runner": "^2.0.0",
"grunt-injector": "^0.6.0",
"grunt-karma": "~0.12.0",
"grunt-build-control": "^0.6.0",
"grunt-contrib-sass": "^0.9.0",
"jit-grunt": "^0.9.1",
"grunt-express-server": "^0.5.1",
"grunt-postcss": "~0.7.1",
"grunt-open": "~0.2.3",
"time-grunt": "^1.2.1",
"grunt-mocha-test": "~0.12.7",
"grunt-mocha-istanbul": "^3.0.1",
"open": "~0.0.4",
"jshint-stylish": "~2.1.0",
"connect-livereload": "^0.5.3",
"istanbul": "~0.4.1",
"chai": "^3.2.0",
"sinon": "^1.16.1",
"chai-as-promised": "^5.1.0",
"chai-things": "^0.2.0",
"karma": "~0.13.3",
"karma-ng-scenario": "~0.1.0",
"karma-firefox-launcher": "~0.1.6",
"karma-script-launcher": "~0.1.0",
"karma-chrome-launcher": "~0.2.0",
"karma-requirejs": "~0.2.2",
"karma-jade-preprocessor": "0.0.11",
"karma-phantomjs-launcher": "~0.2.0",
"karma-ng-html2js-preprocessor": "~0.2.0",
"karma-spec-reporter": "~0.0.20",
"sinon-chai": "^2.8.0",
"mocha": "^2.2.5",
"jasmine-core": "^2.3.4",
"karma-jasmine": "~0.3.0",
"jasmine-spec-reporter": "^2.4.0",
"karma-babel-preprocessor": "^5.2.1",
"requirejs": "~2.1.11",
"phantomjs": "^1.9.18",
"proxyquire": "^1.0.1",
"supertest": "^1.1.0"
},
"engines": {
"node": "^4.2.3",
"npm": "^2.14.7"
},
"scripts": {
"start": "node server",
"test": "grunt test",
"update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
},
"private": true
}
================================================
FILE: protractor.conf.js
================================================
// Protractor configuration
// https://github.com/angular/protractor/blob/master/referenceConf.js
'use strict';
var config = {
// The timeout for each script run on the browser. This should be longer
// than the maximum time your application needs to stabilize between tasks.
allScriptsTimeout: 110000,
// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:' + (process.env.PORT || '9000'),
// Credientials for Saucelabs
sauceUser: process.env.SAUCE_USERNAME,
sauceKey: process.env.SAUCE_ACCESS_KEY,
// list of files / patterns to load in the browser
specs: [
'e2e/**/*.spec.js'
],
// Patterns to exclude.
exclude: [],
// ----- Capabilities to be passed to the webdriver instance ----
//
// For a full list of available capabilities, see
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
// and
// https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
capabilities: {
'browserName': 'chrome',
'name': 'Fullstack E2E',
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'build': process.env.TRAVIS_BUILD_NUMBER
},
// ----- The test framework -----
//
// Jasmine and Cucumber are fully supported as a test and assertion framework.
// Mocha has limited beta support. You will need to include your own
// assertion framework if working with mocha.
framework: 'jasmine2',
// ----- Options to be passed to minijasminenode -----
//
// See the full list at https://github.com/jasmine/jasmine-npm
jasmineNodeOpts: {
defaultTimeoutInterval: 30000,
print: function() {} // for jasmine-spec-reporter
},
// Prepare environment for tests
params: {
serverConfig: require('./server/config/environment')
},
onPrepare: function() {
require('babel-core/register');
var SpecReporter = require('jasmine-spec-reporter');
// add jasmine spec reporter
jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: true}));
var serverConfig = config.params.serverConfig;
// Setup mongo for tests
var mongoose = require('mongoose');
mongoose.connect(serverConfig.mongo.uri, serverConfig.mongo.options); // Connect to database
}
};
config.params.baseUrl = config.baseUrl;
exports.config = config;
================================================
FILE: server/.jshintrc
================================================
{
"expr": true,
"node": true,
"esnext": true,
"bitwise": true,
"eqeqeq": true,
"immed": true,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"undef": true,
"smarttabs": true,
"asi": true,
"debug": true
}
================================================
FILE: server/.jshintrc-spec
================================================
{
"extends": ".jshintrc",
"globals": {
"jasmine": true,
"describe": true,
"it": true,
"before": true,
"beforeEach": true,
"after": true,
"afterEach": true,
"expect": true,
"assert": true,
"sinon": true
}
}
================================================
FILE: server/api/question/index.js
================================================
'use strict';
var express = require('express');
var controller = require('./question.controller');
var router = express.Router();
var auth = require('../../auth/auth.service');
router.get('/', controller.index);
router.get('/:id', controller.show);
router.post('/', auth.isAuthenticated(), controller.create);
router.put('/:id', auth.isAuthenticated(), controller.update);
router.patch('/:id', auth.isAuthenticated(), controller.update);
router.delete('/:id', auth.isAuthenticated(), controller.destroy);
router.post('/:id/answers', auth.isAuthenticated(), controller.createAnswer);
router.put('/:id/answers/:answerId', auth.isAuthenticated(), controller.updateAnswer);
router.delete('/:id/answers/:answerId', auth.isAuthenticated(), controller.destroyAnswer);
router.post('/:id/comments', auth.isAuthenticated(), controller.createComment);
router.put('/:id/comments/:commentId', auth.isAuthenticated(), controller.updateComment);
router.delete('/:id/comments/:commentId', auth.isAuthenticated(), controller.destroyComment);
router.post('/:id/answers/:answerId/comments', auth.isAuthenticated(), controller.createAnswerComment);
router.put('/:id/answers/:answerId/comments/:commentId', auth.isAuthenticated(), controller.updateAnswerComment);
router.delete('/:id/answers/:answerId/comments/:commentId', auth.isAuthenticated(), controller.destroyAnswerComment);
router.put('/:id/star', auth.isAuthenticated(), controller.star);
router.delete('/:id/star', auth.isAuthenticated(), controller.unstar);
router.put('/:id/answers/:answerId/star', auth.isAuthenticated(), controller.starAnswer);
router.delete('/:id/answers/:answerId/star', auth.isAuthenticated(), controller.unstarAnswer);
router.put('/:id/comments/:commentId/star', auth.isAuthenticated(), controller.starComment);
router.delete('/:id/comments/:commentId/star', auth.isAuthenticated(), controller.unstarComment);
router.put('/:id/answers/:answerId/comments/:commentId/star', auth.isAuthenticated(), controller.starAnswerComment);
router.delete('/:id/answers/:answerId/comments/:commentId/star', auth.isAuthenticated(), controller.unstarAnswerComment);
module.exports = router;
================================================
FILE: server/api/question/question.controller.js
================================================
/**
* Using Rails-like standard naming convention for endpoints.
* GET /api/questions -> index
* POST /api/questions -> create
* GET /api/questions/:id -> show
* PUT /api/questions/:id -> update
* DELETE /api/questions/:id -> destroy
*/
'use strict';
import _ from 'lodash';
import Question from './question.model';
function respondWithResult(res, statusCode) {
statusCode = statusCode || 200;
return function(entity) {
if (entity) {
res.status(statusCode).json(entity);
}
};
}
function saveUpdates(updates) {
return function(entity) {
var updated = _.merge(entity, updates);
return updated.saveAsync()
.spread(updated => {
return updated;
});
};
}
function removeEntity(res) {
return function(entity) {
if (entity) {
return entity.removeAsync()
.then(() => {
res.status(204).end();
});
}
};
}
function handleEntityNotFound(res) {
return function(entity) {
if (!entity) {
res.status(404).end();
return null;
}
return entity;
};
}
function handleError(res, statusCode) {
statusCode = statusCode || 500;
return function(err) {
res.status(statusCode).send(err);
};
}
function handleUnauthorized(req, res) {
return function(entity) {
if (!entity) {return null;}
if(entity.user._id.toString() !== req.user._id.toString()){
res.send(403).end();
return null;
}
return entity;
}
}
// Gets a list of Questions
export function index(req, res) {
var query = req.query.query && JSON.parse(req.query.query);
Question.find(query).sort({createdAt: -1}).limit(20).execAsync()
.then(respondWithResult(res))
.catch(handleError(res));
}
// Gets a single Question from the DB
export function show(req, res) {
Question.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(respondWithResult(res))
.catch(handleError(res));
}
// Creates a new Question in the DB
export function create(req, res) {
req.body.user = req.user;
Question.createAsync(req.body)
.then(respondWithResult(res, 201))
.catch(handleError(res));
}
// Updates an existing Question in the DB
export function update(req, res) {
if (req.body._id) {
delete req.body._id;
}
Question.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(handleUnauthorized(req, res))
.then(saveUpdates(req.body))
.then(respondWithResult(res))
.catch(handleError(res));
}
// Deletes a Question from the DB
export function destroy(req, res) {
Question.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(handleUnauthorized(req, res))
.then(removeEntity(res))
.catch(handleError(res));
}
export function createAnswer(req, res) {
req.body.user = req.user;
Question.update({_id: req.params.id}, {$push: {answers: req.body}}, function(err, num) {
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
});
}
export function destroyAnswer(req, res) {
Question.update({_id: req.params.id}, {$pull: {answers: {_id: req.params.answerId , 'user': req.user._id}}}, function(err, num) {
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
});
}
export function updateAnswer(req, res) {
Question.update({_id: req.params.id, 'answers._id': req.params.answerId}, {'answers.$.content': req.body.content, 'answers.$.user': req.user.id}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
});
}
/* comments APIs */
export function createComment(req, res) {
req.body.user = req.user.id;
Question.update({_id: req.params.id}, {$push: {comments: req.body}}, function(err, num){
if(err) {return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
})
}
export function destroyComment(req, res) {
Question.update({_id: req.params.id}, {$pull: {comments: {_id: req.params.commentId , 'user': req.user._id}}}, function(err, num) {
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
});
}
export function updateComment(req, res) {
Question.update({_id: req.params.id, 'comments._id': req.params.commentId}, {'comments.$.content': req.body.content, 'comments.$.user': req.user.id}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
});
}
/* answersComments APIs */
export function createAnswerComment(req, res) {
req.body.user = req.user.id;
Question.update({_id: req.params.id, 'answers._id': req.params.answerId}, {$push: {'answers.$.comments': req.body}}, function(err, num){
if(err) {return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
})
}
export function destroyAnswerComment(req, res) {
Question.update({_id: req.params.id, 'answers._id': req.params.answerId}, {$pull: {'answers.$.comments': {_id: req.params.commentId , 'user': req.user._id}}}, function(err, num) {
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
});
}
export function updateAnswerComment(req, res) {
Question.find({_id: req.params.id}).exec(function(err, questions){
if(err) { return handleError(res)(err); }
if(questions.length === 0) { return res.send(404).end(); }
var question = questions[0];
var found = false;
for(var i=0; i < question.answers.length; i++){
if(question.answers[i]._id.toString() === req.params.answerId){
found = true;
var conditions = {};
conditions._id = req.params.id;
conditions['answers.' + i + '.comments._id'] = req.params.commentId;
conditions['answers.' + i + '.comments.user'] = req.user._id;
var doc = {};
doc['answers.' + i + '.comments.$.content'] = req.body.content;
/*jshint -W083 */
Question.update(conditions, doc, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
Question.updateSearchText(req.params.id);
return;
});
}
}
if(!found){
return res.send(404).end();
}
});
}
/* star/unstar question */
export function star(req, res) {
Question.update({_id: req.params.id}, {$push: {stars: req.user.id}}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
});
}
export function unstar(req, res) {
Question.update({_id: req.params.id}, {$pull: {stars: req.user.id}}, function(err, num){
if(err) { return handleError(res, err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
});
}
/* star/unstar answer */
export function starAnswer(req, res) {
Question.update({_id: req.params.id, 'answers._id': req.params.answerId}, {$push: {'answers.$.stars': req.user.id}}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
});
}
export function unstarAnswer(req, res) {
Question.update({_id: req.params.id, 'answers._id': req.params.answerId}, {$pull: {'answers.$.stars': req.user.id}}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
});
}
/* star/unstar question comment */
export function starComment(req, res) {
Question.update({_id: req.params.id, 'comments._id': req.params.commentId}, {$push: {'comments.$.stars': req.user.id}}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
});
}
export function unstarComment(req, res) {
Question.update({_id: req.params.id, 'comments._id': req.params.commentId}, {$pull: {'comments.$.stars': req.user.id}}, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
});
}
/* star/unstar question answer comment */
var pushOrPullStarAnswerComment = function(op, req, res) {
Question.find({_id: req.params.id}).exec(function(err, questions){
if(err) { return handleError(res)(err); }
if(questions.length === 0) { return res.send(404).end(); }
var question = questions[0];
var found = false;
for(var i=0; i < question.answers.length; i++){
if(question.answers[i]._id.toString() === req.params.answerId){
found = true;
var conditions = {};
conditions._id = req.params.id;
conditions['answers.' + i + '.comments._id'] = req.params.commentId;
var doc = {};
doc[op] = {};
doc[op]['answers.' + i + '.comments.$.stars'] = req.user.id;
// Question.update({_id: req.params.id, 'answers.' + i + '.comments._id': req.params.commentId}, {op: {('answers.' + i + '.comments.$.stars'): req.user.id}}, function(err, num){
/*jshint -W083 */
Question.update(conditions, doc, function(err, num){
if(err) { return handleError(res)(err); }
if(num === 0) { return res.send(404).end(); }
exports.show(req, res);
return;
});
}
}
if(!found){
return res.send(404).end();
}
});
};
export function starAnswerComment(req, res) {
pushOrPullStarAnswerComment('$push', req, res);
}
export function unstarAnswerComment(req, res) {
pushOrPullStarAnswerComment('$pull', req, res);
}
================================================
FILE: server/api/question/question.events.js
================================================
/**
* Question model events
*/
'use strict';
import {EventEmitter} from 'events';
var Question = require('./question.model');
var QuestionEvents = new EventEmitter();
// Set max event listeners (0 == unlimited)
QuestionEvents.setMaxListeners(0);
// Model events
var events = {
'save': 'save',
'remove': 'remove'
};
// Register the event emitter to the model events
for (var e in events) {
var event = events[e];
Question.schema.post(e, emitEvent(event));
}
function emitEvent(event) {
return function(doc) {
QuestionEvents.emit(event + ':' + doc._id, doc);
QuestionEvents.emit(event, doc);
}
}
export default QuestionEvents;
================================================
FILE: server/api/question/question.integration.js
================================================
'use strict';
var app = require('../..');
import request from 'supertest';
var User = require('../user/user.model');
var newQuestion;
describe('Question API:', function() {
var user;
before(function() {
return User.removeAsync().then(function() {
user = new User({
name: 'Fake User',
email: 'test@test.com',
password: 'password'
});
return user.saveAsync();
});
});
var token;
before(function(done) {
request(app)
.post('/auth/local')
.send({
email: 'test@test.com',
password: 'password'
})
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
token = res.body.token;
done();
});
});
describe('GET /api/questions', function() {
var questions;
beforeEach(function(done) {
request(app)
.get('/api/questions')
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
questions = res.body;
done();
});
});
it('should respond with JSON array', function() {
questions.should.be.instanceOf(Array);
});
});
describe('POST /api/questions', function() {
beforeEach(function(done) {
request(app)
.post('/api/questions')
.set('authorization', 'Bearer ' + token)
.send({
title: 'New Question',
content: 'This is the brand new question!!!'
})
.expect(201)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
newQuestion = res.body;
console.warn("newQuestion:test1",newQuestion);
done();
});
});
it('should respond with the newly created question', function() {
console.warn("newQuestion:test2",newQuestion);
newQuestion.title.should.equal('New Question');
newQuestion.content.should.equal('This is the brand new question!!!');
});
});
describe('GET /api/questions/:id', function() {
var question;
beforeEach(function(done) {
request(app)
.get('/api/questions/' + newQuestion._id)
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
question = res.body;
done();
});
});
afterEach(function() {
question = {};
});
it('should respond with the requested question', function() {
question.title.should.equal('New Question');
question.content.should.equal('This is the brand new question!!!');
});
});
/*
describe('PUT /api/questions/:id', function() {
var updatedQuestion;
beforeEach(function(done) {
request(app)
.put('/api/questions/' + newQuestion._id)
.set('authorization', 'Bearer ' + token)
.send({
title: 'Updated Question',
content: 'This is the updated question!!!'
})
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) {
return done(err);
}
updatedQuestion = res.body;
done();
});
});
afterEach(function() {
updatedQuestion = {};
});
it('should respond with the updated question', function() {
updatedQuestion.title.should.equal('Updated Question');
updatedQuestion.content.should.equal('This is the updated question!!!');
});
});
*/
describe('DELETE /api/questions/:id', function() {
it('should respond with 204 on successful removal', function(done) {
request(app)
.delete('/api/questions/' + newQuestion._id)
.set('authorization', 'Bearer ' + token)
.expect(204)
.end((err, res) => {
if (err) {
return done(err);
}
done();
});
});
it('should respond with 404 when question does not exist', function(done) {
request(app)
.delete('/api/questions/' + newQuestion._id)
.set('authorization', 'Bearer ' + token)
.expect(404)
.end((err, res) => {
if (err) {
return done(err);
}
done();
});
});
});
});
================================================
FILE: server/api/question/question.model.js
================================================
'use strict';
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var QuestionSchema = new mongoose.Schema({
title: String,
content: String,
answers: [{
content: String,
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now,
},
comments: [{
content: String,
stars: [{
type: mongoose.Schema.ObjectId,
ref: 'User'
}],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now,
}
}],
stars: [{
type: mongoose.Schema.ObjectId,
ref: 'User'
}],
}],
tags: [{
text: String,
}],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now
},
comments: [{
content: String,
stars: [{
type: mongoose.Schema.ObjectId,
ref: 'User'
}],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
createdAt: {
type: Date,
default: Date.now,
}
}],
stars: [{
type: mongoose.Schema.ObjectId,
ref: 'User'
}],
searchText: String,
});
QuestionSchema.pre('find', function(next){
this.populate('user', 'name');
this.populate('comments.user', 'name');
this.populate('answers.user', 'name');
this.populate('answers.comments.user', 'name');
next();
});
QuestionSchema.pre('findOne', function(next){
this.populate('user', 'name');
this.populate('comments.user', 'name');
this.populate('answers.user', 'name');
this.populate('answers.comments.user', 'name');
next();
});
QuestionSchema.index({
'title': 'text',
'content': 'text',
'tags.text': 'text',
'answers.content': 'text',
'comments.content': 'text',
'answers.comments.content': 'text',
'searchText': 'text',
}, {name: 'question_schema_index'});
var TinySegmenter = require('tiny-segmenter');
var getSearchText = function(question){
var tinySegmenter = new TinySegmenter();
var searchText = "";
searchText += tinySegmenter.segment(question.title).join(' ') + " ";
searchText += tinySegmenter.segment(question.content).join(' ') + " ";
question.answers.forEach(function(answer){
searchText += tinySegmenter.segment(answer.content).join(' ') + " ";
answer.comments.forEach(function(comment){
searchText += tinySegmenter.segment(comment.content).join(' ') + " ";
});
});
question.comments.forEach(function(comment){
searchText += tinySegmenter.segment(comment.content).join(' ') + " ";
});
console.log("searchText", searchText);
return searchText;
};
QuestionSchema.statics.updateSearchText = function(id, cb){
this.findOne({_id: id}).exec(function(err, question){
if(err){ if(cb){cb(err);} return; }
var searchText = getSearchText(question);
this.update({_id: id}, {searchText: searchText}, function(err, num){
if(cb){cb(err);}
});
}.bind(this));
};
QuestionSchema.pre('save', function(next){
this.searchText = getSearchText(this);
next();
});
export default mongoose.model('Question', QuestionSchema);
================================================
FILE: server/api/question/question.socket.js
================================================
/**
* Broadcast updates to client when the model changes
*/
'use strict';
var QuestionEvents = require('./question.events');
// Model events to emit
var events = ['save', 'remove'];
export function register(socket) {
// Bind model events to socket events
for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
var event = events[i];
var listener = createListener('question:' + event, socket);
QuestionEvents.on(event, listener);
socket.on('disconnect', removeListener(event, listener));
}
}
function createListener(event, socket) {
return function(doc) {
socket.emit(event, doc);
};
}
function removeListener(event, listener) {
return function() {
QuestionEvents.removeListener(event, listener);
};
}
================================================
FILE: server/api/thing/index.js
================================================
'use strict';
var express = require('express');
var controller = require('./thing.controller');
var router = express.Router();
router.get('/', controller.index);
router.get('/:id', controller.show);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.patch('/:id', controller.update);
router.delete('/:id', controller.destroy);
module.exports = router;
================================================
FILE: server/api/thing/index.spec.js
================================================
'use strict';
var proxyquire = require('proxyquire').noPreserveCache();
var thingCtrlStub = {
index: 'thingCtrl.index',
show: 'thingCtrl.show',
create: 'thingCtrl.create',
update: 'thingCtrl.update',
destroy: 'thingCtrl.destroy'
};
var routerStub = {
get: sinon.spy(),
put: sinon.spy(),
patch: sinon.spy(),
post: sinon.spy(),
delete: sinon.spy()
};
// require the index with our stubbed out modules
var thingIndex = proxyquire('./index.js', {
'express': {
Router: function() {
return routerStub;
}
},
'./thing.controller': thingCtrlStub
});
describe('Thing API Router:', function() {
it('should return an express router instance', function() {
thingIndex.should.equal(routerStub);
});
describe('GET /api/things', function() {
it('should route to thing.controller.index', function() {
routerStub.get
.withArgs('/', 'thingCtrl.index')
.should.have.been.calledOnce;
});
});
describe('GET /api/things/:id', function() {
it('should route to thing.controller.show', function() {
routerStub.get
.withArgs('/:id', 'thingCtrl.show')
.should.have.been.calledOnce;
});
});
describe('POST /api/things', function() {
it('should route to thing.controller.create', function() {
routerStub.post
.withArgs('/', 'thingCtrl.create')
.should.have.been.calledOnce;
});
});
describe('PUT /api/things/:id', function() {
it('should route to thing.controller.update', function() {
routerStub.put
.withArgs('/:id', 'thingCtrl.update')
.should.have.been.calledOnce;
});
});
describe('PATCH /api/things/:id', function() {
it('should route to thing.controller.update', function() {
routerStub.patch
.withArgs('/:id', 'thingCtrl.update')
.should.have.been.calledOnce;
});
});
describe('DELETE /api/things/:id', function() {
it('should route to thing.controller.destroy', function() {
routerStub.delete
.withArgs('/:id', 'thingCtrl.destroy')
.should.have.been.calledOnce;
});
});
});
================================================
FILE: server/api/thing/thing.controller.js
================================================
/**
* Using Rails-like standard naming convention for endpoints.
* GET /api/things -> index
* POST /api/things -> create
* GET /api/things/:id -> show
* PUT /api/things/:id -> update
* DELETE /api/things/:id -> destroy
*/
'use strict';
import _ from 'lodash';
import Thing from './thing.model';
function respondWithResult(res, statusCode) {
statusCode = statusCode || 200;
return function(entity) {
if (entity) {
res.status(statusCode).json(entity);
}
};
}
function saveUpdates(updates) {
return function(entity) {
var updated = _.merge(entity, updates);
return updated.saveAsync()
.spread(updated => {
return updated;
});
};
}
function removeEntity(res) {
return function(entity) {
if (entity) {
return entity.removeAsync()
.then(() => {
res.status(204).end();
});
}
};
}
function handleEntityNotFound(res) {
return function(entity) {
if (!entity) {
res.status(404).end();
return null;
}
return entity;
};
}
function handleError(res, statusCode) {
statusCode = statusCode || 500;
return function(err) {
res.status(statusCode).send(err);
};
}
// Gets a list of Things
export function index(req, res) {
Thing.findAsync()
.then(respondWithResult(res))
.catch(handleError(res));
}
// Gets a single Thing from the DB
export function show(req, res) {
Thing.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(respondWithResult(res))
.catch(handleError(res));
}
// Creates a new Thing in the DB
export function create(req, res) {
Thing.createAsync(req.body)
.then(respondWithResult(res, 201))
.catch(handleError(res));
}
// Updates an existing Thing in the DB
export function update(req, res) {
if (req.body._id) {
delete req.body._id;
}
Thing.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(saveUpdates(req.body))
.then(respondWithResult(res))
.catch(handleError(res));
}
// Deletes a Thing from the DB
export function destroy(req, res) {
Thing.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(removeEntity(res))
.catch(handleError(res));
}
================================================
FILE: server/api/thing/thing.events.js
================================================
/**
* Thing model events
*/
'use strict';
import {EventEmitter} from 'events';
var Thing = require('./thing.model');
var ThingEvents = new EventEmitter();
// Set max event listeners (0 == unlimited)
ThingEvents.setMaxListeners(0);
// Model events
var events = {
'save': 'save',
'remove': 'remove'
};
// Register the event emitter to the model events
for (var e in events) {
var event = events[e];
Thing.schema.post(e, emitEvent(event));
}
function emitEvent(event) {
return function(doc) {
ThingEvents.emit(event + ':' + doc._id, doc);
ThingEvents.emit(event, doc);
}
}
export default ThingEvents;
================================================
FILE: server/api/thing/thing.integration.js
================================================
'use strict';
var app = require('../..');
import request from 'supertest';
var newThing;
describe('Thing API:', function() {
describe('GET /api/things', function() {
var things;
beforeEach(function(done) {
request(app)
.get('/api/things')
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
things = res.body;
done();
});
});
it('should respond with JSON array', function() {
things.should.be.instanceOf(Array);
});
});
describe('POST /api/things', function() {
beforeEach(function(done) {
request(app)
.post('/api/things')
.send({
name: 'New Thing',
info: 'This is the brand new thing!!!'
})
.expect(201)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
newThing = res.body;
done();
});
});
it('should respond with the newly created thing', function() {
newThing.name.should.equal('New Thing');
newThing.info.should.equal('This is the brand new thing!!!');
});
});
describe('GET /api/things/:id', function() {
var thing;
beforeEach(function(done) {
request(app)
.get('/api/things/' + newThing._id)
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
thing = res.body;
done();
});
});
afterEach(function() {
thing = {};
});
it('should respond with the requested thing', function() {
thing.name.should.equal('New Thing');
thing.info.should.equal('This is the brand new thing!!!');
});
});
describe('PUT /api/things/:id', function() {
var updatedThing;
beforeEach(function(done) {
request(app)
.put('/api/things/' + newThing._id)
.send({
name: 'Updated Thing',
info: 'This is the updated thing!!!'
})
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) {
return done(err);
}
updatedThing = res.body;
done();
});
});
afterEach(function() {
updatedThing = {};
});
it('should respond with the updated thing', function() {
updatedThing.name.should.equal('Updated Thing');
updatedThing.info.should.equal('This is the updated thing!!!');
});
});
describe('DELETE /api/things/:id', function() {
it('should respond with 204 on successful removal', function(done) {
request(app)
.delete('/api/things/' + newThing._id)
.expect(204)
.end((err, res) => {
if (err) {
return done(err);
}
done();
});
});
it('should respond with 404 when thing does not exist', function(done) {
request(app)
.delete('/api/things/' + newThing._id)
.expect(404)
.end((err, res) => {
if (err) {
return done(err);
}
done();
});
});
});
});
================================================
FILE: server/api/thing/thing.model.js
================================================
'use strict';
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var ThingSchema = new mongoose.Schema({
name: String,
info: String,
active: Boolean
});
export default mongoose.model('Thing', ThingSchema);
================================================
FILE: server/api/thing/thing.socket.js
================================================
/**
* Broadcast updates to client when the model changes
*/
'use strict';
var ThingEvents = require('./thing.events');
// Model events to emit
var events = ['save', 'remove'];
export function register(socket) {
// Bind model events to socket events
for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
var event = events[i];
var listener = createListener('thing:' + event, socket);
ThingEvents.on(event, listener);
socket.on('disconnect', removeListener(event, listener));
}
}
function createListener(event, socket) {
return function(doc) {
socket.emit(event, doc);
};
}
function removeListener(event, listener) {
return function() {
ThingEvents.removeListener(event, listener);
};
}
================================================
FILE: server/api/user/index.js
================================================
'use strict';
import {Router} from 'express';
import * as controller from './user.controller';
import * as auth from '../../auth/auth.service';
var router = new Router();
router.get('/', auth.hasRole('admin'), controller.index);
router.delete('/:id', auth.hasRole('admin'), controller.destroy);
router.get('/me', auth.isAuthenticated(), controller.me);
router.put('/:id/password', auth.isAuthenticated(), controller.changePassword);
router.get('/:id', auth.isAuthenticated(), controller.show);
router.post('/', controller.create);
export default router;
================================================
FILE: server/api/user/index.spec.js
================================================
'use strict';
var proxyquire = require('proxyquire').noPreserveCache();
var userCtrlStub = {
index: 'userCtrl.index',
destroy: 'userCtrl.destroy',
me: 'userCtrl.me',
changePassword: 'userCtrl.changePassword',
show: 'userCtrl.show',
create: 'userCtrl.create'
};
var authServiceStub = {
isAuthenticated() {
return 'authService.isAuthenticated';
},
hasRole(role) {
return 'authService.hasRole.' + role;
}
};
var routerStub = {
get: sinon.spy(),
put: sinon.spy(),
post: sinon.spy(),
delete: sinon.spy()
};
// require the index with our stubbed out modules
var userIndex = proxyquire('./index', {
'express': {
Router() {
return routerStub;
}
},
'./user.controller': userCtrlStub,
'../../auth/auth.service': authServiceStub
});
describe('User API Router:', function() {
it('should return an express router instance', function() {
userIndex.should.equal(routerStub);
});
describe('GET /api/users', function() {
it('should verify admin role and route to user.controller.index', function() {
routerStub.get
.withArgs('/', 'authService.hasRole.admin', 'userCtrl.index')
.should.have.been.calledOnce;
});
});
describe('DELETE /api/users/:id', function() {
it('should verify admin role and route to user.controller.destroy', function() {
routerStub.delete
.withArgs('/:id', 'authService.hasRole.admin', 'userCtrl.destroy')
.should.have.been.calledOnce;
});
});
describe('GET /api/users/me', function() {
it('should be authenticated and route to user.controller.me', function() {
routerStub.get
.withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me')
.should.have.been.calledOnce;
});
});
describe('PUT /api/users/:id/password', function() {
it('should be authenticated and route to user.controller.changePassword', function() {
routerStub.put
.withArgs('/:id/password', 'authService.isAuthenticated', 'userCtrl.changePassword')
.should.have.been.calledOnce;
});
});
describe('GET /api/users/:id', function() {
it('should be authenticated and route to user.controller.show', function() {
routerStub.get
.withArgs('/:id', 'authService.isAuthenticated', 'userCtrl.show')
.should.have.been.calledOnce;
});
});
describe('POST /api/users', function() {
it('should route to user.controller.create', function() {
routerStub.post
.withArgs('/', 'userCtrl.create')
.should.have.been.calledOnce;
});
});
});
================================================
FILE: server/api/user/user.controller.js
================================================
'use strict';
import User from './user.model';
import passport from 'passport';
import config from '../../config/environment';
import jwt from 'jsonwebtoken';
function validationError(res, statusCode) {
statusCode = statusCode || 422;
return function(err) {
res.status(statusCode).json(err);
}
}
function handleError(res, statusCode) {
statusCode = statusCode || 500;
return function(err) {
res.status(statusCode).send(err);
};
}
/**
* Get list of users
* restriction: 'admin'
*/
export function index(req, res) {
User.findAsync({}, '-salt -password')
.then(users => {
res.status(200).json(users);
})
.catch(handleError(res));
}
/**
* Creates a new user
*/
export function create(req, res, next) {
var newUser = new User(req.body);
newUser.provider = 'local';
newUser.role = 'user';
newUser.saveAsync()
.spread(function(user) {
var token = jwt.sign({ _id: user._id }, config.secrets.session, {
expiresIn: 60 * 60 * 5
});
res.json({ token });
})
.catch(validationError(res));
}
/**
* Get a single user
*/
export function show(req, res, next) {
var userId = req.params.id;
User.findByIdAsync(userId)
.then(user => {
if (!user) {
return res.status(404).end();
}
res.json(user.profile);
})
.catch(err => next(err));
}
/**
* Deletes a user
* restriction: 'admin'
*/
export function destroy(req, res) {
User.findByIdAndRemoveAsync(req.params.id)
.then(function() {
res.status(204).end();
})
.catch(handleError(res));
}
/**
* Change a users password
*/
export function changePassword(req, res, next) {
var userId = req.user._id;
var oldPass = String(req.body.oldPassword);
var newPass = String(req.body.newPassword);
User.findByIdAsync(userId)
.then(user => {
if (user.authenticate(oldPass)) {
user.password = newPass;
return user.saveAsync()
.then(() => {
res.status(204).end();
})
.catch(validationError(res));
} else {
return res.status(403).end();
}
});
}
/**
* Get my info
*/
export function me(req, res, next) {
var userId = req.user._id;
User.findOneAsync({ _id: userId }, '-salt -password')
.then(user => { // don't ever give out the password or salt
if (!user) {
return res.status(401).end();
}
res.json(user);
})
.catch(err => next(err));
}
/**
* Authentication callback
*/
export function authCallback(req, res, next) {
res.redirect('/');
}
================================================
FILE: server/api/user/user.events.js
================================================
/**
* User model events
*/
'use strict';
import {EventEmitter} from 'events';
import User from './user.model';
var UserEvents = new EventEmitter();
// Set max event listeners (0 == unlimited)
UserEvents.setMaxListeners(0);
// Model events
var events = {
'save': 'save',
'remove': 'remove'
};
// Register the event emitter to the model events
for (var e in events) {
var event = events[e];
User.schema.post(e, emitEvent(event));
}
function emitEvent(event) {
return function(doc) {
UserEvents.emit(event + ':' + doc._id, doc);
UserEvents.emit(event, doc);
}
}
export default UserEvents;
================================================
FILE: server/api/user/user.integration.js
================================================
'use strict';
import app from '../..';
import User from './user.model';
import request from 'supertest';
describe('User API:', function() {
var user;
// Clear users before testing
before(function() {
return User.removeAsync().then(function() {
user = new User({
name: 'Fake User',
email: 'test@example.com',
password: 'password'
});
return user.saveAsync();
});
});
// Clear users after testing
after(function() {
return User.removeAsync();
});
describe('GET /api/users/me', function() {
var token;
before(function(done) {
request(app)
.post('/auth/local')
.send({
email: 'test@example.com',
password: 'password'
})
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
token = res.body.token;
done();
});
});
it('should respond with a user profile when authenticated', function(done) {
request(app)
.get('/api/users/me')
.set('authorization', 'Bearer ' + token)
.expect(200)
.expect('Content-Type', /json/)
.end((err, res) => {
res.body._id.toString().should.equal(user._id.toString());
done();
});
});
it('should respond with a 401 when not authenticated', function(done) {
request(app)
.get('/api/users/me')
.expect(401)
.end(done);
});
});
});
================================================
FILE: server/api/user/user.model.js
================================================
'use strict';
import crypto from 'crypto';
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
import {Schema} from 'mongoose';
const authTypes = ['github', 'twitter', 'facebook', 'google'];
var UserSchema = new Schema({
name: String,
email: {
type: String,
lowercase: true
},
role: {
type: String,
default: 'user'
},
password: String,
provider: String,
salt: String,
facebook: {},
twitter: {},
google: {},
github: {}
});
/**
* Virtuals
*/
// Public profile information
UserSchema
.virtual('profile')
.get(function() {
return {
'name': this.name,
'role': this.role
};
});
// Non-sensitive info we'll be putting in the token
UserSchema
.virtual('token')
.get(function() {
return {
'_id': this._id,
'role': this.role
};
});
/**
* Validations
*/
// Validate empty email
UserSchema
.path('email')
.validate(function(email) {
if (authTypes.indexOf(this.provider) !== -1) {
return true;
}
return email.length;
}, 'Email cannot be blank');
// Validate empty password
UserSchema
.path('password')
.validate(function(password) {
if (authTypes.indexOf(this.provider) !== -1) {
return true;
}
return password.length;
}, 'Password cannot be blank');
// Validate email is not taken
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
return this.constructor.findOneAsync({ email: value })
.then(function(user) {
if (user) {
if (self.id === user.id) {
return respond(true);
}
return respond(false);
}
return respond(true);
})
.catch(function(err) {
throw err;
});
}, 'The specified email address is already in use.');
var validatePresenceOf = function(value) {
return value && value.length;
};
/**
* Pre-save hook
*/
UserSchema
.pre('save', function(next) {
// Handle new/update passwords
if (!this.isModified('password')) {
return next();
}
if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) {
next(new Error('Invalid password'));
}
// Make salt with a callback
this.makeSalt((saltErr, salt) => {
if (saltErr) {
next(saltErr);
}
this.salt = salt;
this.encryptPassword(this.password, (encryptErr, hashedPassword) => {
if (encryptErr) {
next(encryptErr);
}
this.password = hashedPassword;
next();
});
});
});
/**
* Methods
*/
UserSchema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* @param {String} password
* @param {Function} callback
* @return {Boolean}
* @api public
*/
authenticate(password, callback) {
if (!callback) {
return this.password === this.encryptPassword(password);
}
this.encryptPassword(password, (err, pwdGen) => {
if (err) {
return callback(err);
}
if (this.password === pwdGen) {
callback(null, true);
} else {
callback(null, false);
}
});
},
/**
* Make salt
*
* @param {Number} byteSize Optional salt byte size, default to 16
* @param {Function} callback
* @return {String}
* @api public
*/
makeSalt(byteSize, callback) {
var defaultByteSize = 16;
if (typeof arguments[0] === 'function') {
callback = arguments[0];
byteSize = defaultByteSize;
} else if (typeof arguments[1] === 'function') {
callback = arguments[1];
}
if (!byteSize) {
byteSize = defaultByteSize;
}
if (!callback) {
return crypto.randomBytes(byteSize).toString('base64');
}
return crypto.randomBytes(byteSize, (err, salt) => {
if (err) {
callback(err);
} else {
callback(null, salt.toString('base64'));
}
});
},
/**
* Encrypt password
*
* @param {String} password
* @param {Function} callback
* @return {String}
* @api public
*/
encryptPassword(password, callback) {
if (!password || !this.salt) {
return null;
}
var defaultIterations = 10000;
var defaultKeyLength = 64;
var salt = new Buffer(this.salt, 'base64');
if (!callback) {
return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength)
.toString('base64');
}
return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength, (err, key) => {
if (err) {
callback(err);
} else {
callback(null, key.toString('base64'));
}
});
}
};
export default mongoose.model('User', UserSchema);
================================================
FILE: server/api/user/user.model.spec.js
================================================
'use strict';
import app from '../..';
import User from './user.model';
var user;
var genUser = function() {
user = new User({
provider: 'local',
name: 'Fake User',
email: 'test@example.com',
password: 'password'
});
return user;
};
describe('User Model', function() {
before(function() {
// Clear users before testing
return User.removeAsync();
});
beforeEach(function() {
genUser();
});
afterEach(function() {
return User.removeAsync();
});
it('should begin with no users', function() {
return User.findAsync({}).should
.eventually.have.length(0);
});
it('should fail when saving a duplicate user', function() {
return user.saveAsync()
.then(function() {
var userDup = genUser();
return userDup.saveAsync();
}).should.be.rejected;
});
describe('#email', function() {
it('should fail when saving without an email', function() {
user.email = '';
return user.saveAsync().should.be.rejected;
});
});
describe('#password', function() {
beforeEach(function() {
return user.saveAsync();
});
it('should authenticate user if valid', function() {
user.authenticate('password').should.be.true;
});
it('should not authenticate user if invalid', function() {
user.authenticate('blah').should.not.be.true;
});
it('should remain the same hash unless the password is updated', function() {
user.name = 'Test User';
return user.saveAsync()
.spread(function(u) {
return u.authenticate('password');
}).should.eventually.be.true;
});
});
});
================================================
FILE: server/app.js
================================================
/**
* Main application file
*/
'use strict';
import express from 'express';
import mongoose from 'mongoose';
mongoose.Promise = require('bluebird');
import config from './config/environment';
import http from 'http';
// Connect to MongoDB
mongoose.connect(config.mongo.uri, config.mongo.options);
mongoose.connection.on('error', function(err) {
console.error('MongoDB connection error: ' + err);
process.exit(-1);
});
// Populate databases with sample data
if (config.seedDB) { require('./config/seed'); }
// Setup server
var app = express();
var server = http.createServer(app);
var socketio = require('socket.io')(server, {
serveClient: config.env !== 'production',
path: '/socket.io-client'
});
require('./config/socketio')(socketio);
require('./config/express')(app);
require('./routes')(app);
// Start server
function startServer() {
app.angularFullstack = server.listen(config.port, config.ip, function() {
console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
});
}
setImmediate(startServer);
// Expose app
exports = module.exports = app;
================================================
FILE: server/auth/auth.service.js
================================================
'use strict';
import passport from 'passport';
import config from '../config/environment';
import jwt from 'jsonwebtoken';
import expressJwt
gitextract_m_s_vier/
├── .bowerrc
├── .buildignore
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jscsrc
├── .travis.yml
├── .yo-rc.json
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── client/
│ ├── .htaccess
│ ├── .jshintrc
│ ├── app/
│ │ ├── account/
│ │ │ ├── account.js
│ │ │ ├── login/
│ │ │ │ ├── login.controller.js
│ │ │ │ └── login.html
│ │ │ ├── settings/
│ │ │ │ ├── settings.controller.js
│ │ │ │ └── settings.html
│ │ │ └── signup/
│ │ │ ├── signup.controller.js
│ │ │ └── signup.html
│ │ ├── admin/
│ │ │ ├── admin.controller.js
│ │ │ ├── admin.html
│ │ │ ├── admin.module.js
│ │ │ ├── admin.router.js
│ │ │ └── admin.scss
│ │ ├── app.constant.js
│ │ ├── app.js
│ │ ├── app.scss
│ │ ├── fromNow/
│ │ │ ├── fromNow.filter.js
│ │ │ └── fromNow.filter.spec.js
│ │ ├── questionsCreate/
│ │ │ ├── questionsCreate.controller.js
│ │ │ ├── questionsCreate.controller.spec.js
│ │ │ ├── questionsCreate.html
│ │ │ ├── questionsCreate.js
│ │ │ └── questionsCreate.scss
│ │ ├── questionsIndex/
│ │ │ ├── questionsIndex.controller.js
│ │ │ ├── questionsIndex.controller.spec.js
│ │ │ ├── questionsIndex.html
│ │ │ ├── questionsIndex.js
│ │ │ └── questionsIndex.scss
│ │ └── questionsShow/
│ │ ├── questionsShow.controller.js
│ │ ├── questionsShow.controller.spec.js
│ │ ├── questionsShow.html
│ │ ├── questionsShow.js
│ │ └── questionsShow.scss
│ ├── components/
│ │ ├── auth/
│ │ │ ├── auth.module.js
│ │ │ ├── auth.service.js
│ │ │ ├── interceptor.service.js
│ │ │ ├── router.decorator.js
│ │ │ └── user.service.js
│ │ ├── footer/
│ │ │ ├── footer.directive.js
│ │ │ ├── footer.html
│ │ │ └── footer.scss
│ │ ├── modal/
│ │ │ ├── modal.html
│ │ │ ├── modal.scss
│ │ │ └── modal.service.js
│ │ ├── mongoose-error/
│ │ │ └── mongoose-error.directive.js
│ │ ├── navbar/
│ │ │ ├── navbar.controller.js
│ │ │ ├── navbar.directive.js
│ │ │ └── navbar.html
│ │ ├── oauth-buttons/
│ │ │ ├── oauth-buttons.controller.js
│ │ │ ├── oauth-buttons.controller.spec.js
│ │ │ ├── oauth-buttons.directive.js
│ │ │ ├── oauth-buttons.directive.spec.js
│ │ │ ├── oauth-buttons.html
│ │ │ └── oauth-buttons.scss
│ │ ├── socket/
│ │ │ ├── socket.mock.js
│ │ │ └── socket.service.js
│ │ ├── ui-router/
│ │ │ └── ui-router.mock.js
│ │ └── util/
│ │ ├── util.module.js
│ │ └── util.service.js
│ ├── index.html
│ └── robots.txt
├── e2e/
│ ├── account/
│ │ ├── login/
│ │ │ ├── login.po.js
│ │ │ └── login.spec.js
│ │ ├── logout/
│ │ │ └── logout.spec.js
│ │ └── signup/
│ │ ├── signup.po.js
│ │ └── signup.spec.js
│ ├── components/
│ │ ├── navbar/
│ │ │ └── navbar.po.js
│ │ └── oauth-buttons/
│ │ └── oauth-buttons.po.js
│ └── main/
│ ├── main.po.js
│ └── main.spec.js
├── karma.conf.js
├── mocha.conf.js
├── package.json
├── protractor.conf.js
└── server/
├── .jshintrc
├── .jshintrc-spec
├── api/
│ ├── question/
│ │ ├── index.js
│ │ ├── question.controller.js
│ │ ├── question.events.js
│ │ ├── question.integration.js
│ │ ├── question.model.js
│ │ └── question.socket.js
│ ├── thing/
│ │ ├── index.js
│ │ ├── index.spec.js
│ │ ├── thing.controller.js
│ │ ├── thing.events.js
│ │ ├── thing.integration.js
│ │ ├── thing.model.js
│ │ └── thing.socket.js
│ └── user/
│ ├── index.js
│ ├── index.spec.js
│ ├── user.controller.js
│ ├── user.events.js
│ ├── user.integration.js
│ ├── user.model.js
│ └── user.model.spec.js
├── app.js
├── auth/
│ ├── auth.service.js
│ ├── facebook/
│ │ ├── index.js
│ │ └── passport.js
│ ├── google/
│ │ ├── index.js
│ │ └── passport.js
│ ├── index.js
│ ├── local/
│ │ ├── index.js
│ │ └── passport.js
│ └── twitter/
│ ├── index.js
│ └── passport.js
├── components/
│ └── errors/
│ └── index.js
├── config/
│ ├── environment/
│ │ ├── development.js
│ │ ├── index.js
│ │ ├── production.js
│ │ ├── shared.js
│ │ └── test.js
│ ├── express.js
│ ├── local.env.sample.js
│ ├── seed.js
│ └── socketio.js
├── index.js
├── routes.js
└── views/
└── 404.html
SYMBOL INDEX (97 symbols across 29 files)
FILE: client/app/account/login/login.controller.js
class LoginController (line 3) | class LoginController {
method constructor (line 4) | constructor(Auth, $state) {
method login (line 13) | login(form) {
FILE: client/app/account/settings/settings.controller.js
class SettingsController (line 3) | class SettingsController {
method constructor (line 4) | constructor(Auth) {
method changePassword (line 11) | changePassword(form) {
FILE: client/app/account/signup/signup.controller.js
class SignupController (line 3) | class SignupController {
method constructor (line 10) | constructor(Auth, $state) {
method register (line 15) | register(form) {
FILE: client/app/admin/admin.controller.js
class AdminController (line 5) | class AdminController {
method constructor (line 6) | constructor(User) {
method delete (line 11) | delete(user) {
FILE: client/components/auth/auth.service.js
function AuthService (line 5) | function AuthService($location, $http, $cookies, $q, appConfig, Util, Us...
FILE: client/components/auth/interceptor.service.js
function authInterceptor (line 5) | function authInterceptor($rootScope, $q, $cookies, $injector, Util) {
FILE: client/components/auth/user.service.js
function UserResource (line 5) | function UserResource($resource) {
FILE: client/components/modal/modal.service.js
function openModal (line 11) | function openModal(scope = {}, modalClass = 'modal-default') {
method delete (line 34) | delete(del = angular.noop) {
FILE: client/components/navbar/navbar.controller.js
class NavbarController (line 3) | class NavbarController {
method constructor (line 10) | constructor(Auth, $state) {
FILE: client/components/socket/socket.service.js
method syncUpdates (line 28) | syncUpdates(modelName, array, cb) {
method unsyncUpdates (line 66) | unsyncUpdates(modelName) {
FILE: client/components/util/util.service.js
function UtilService (line 8) | function UtilService($window) {
FILE: server/api/question/question.controller.js
function respondWithResult (line 15) | function respondWithResult(res, statusCode) {
function saveUpdates (line 24) | function saveUpdates(updates) {
function removeEntity (line 34) | function removeEntity(res) {
function handleEntityNotFound (line 45) | function handleEntityNotFound(res) {
function handleError (line 55) | function handleError(res, statusCode) {
function handleUnauthorized (line 61) | function handleUnauthorized(req, res) {
function index (line 72) | function index(req, res) {
function show (line 80) | function show(req, res) {
function create (line 88) | function create(req, res) {
function update (line 96) | function update(req, res) {
function destroy (line 109) | function destroy(req, res) {
function createAnswer (line 117) | function createAnswer(req, res) {
function destroyAnswer (line 127) | function destroyAnswer(req, res) {
function updateAnswer (line 136) | function updateAnswer(req, res) {
function createComment (line 146) | function createComment(req, res) {
function destroyComment (line 155) | function destroyComment(req, res) {
function updateComment (line 163) | function updateComment(req, res) {
function createAnswerComment (line 173) | function createAnswerComment(req, res) {
function destroyAnswerComment (line 182) | function destroyAnswerComment(req, res) {
function updateAnswerComment (line 190) | function updateAnswerComment(req, res) {
function star (line 235) | function star(req, res) {
function unstar (line 242) | function unstar(req, res) {
function starAnswer (line 251) | function starAnswer(req, res) {
function unstarAnswer (line 258) | function unstarAnswer(req, res) {
function starComment (line 267) | function starComment(req, res) {
function unstarComment (line 274) | function unstarComment(req, res) {
function starAnswerComment (line 313) | function starAnswerComment(req, res) {
function unstarAnswerComment (line 316) | function unstarAnswerComment(req, res) {
FILE: server/api/question/question.events.js
function emitEvent (line 26) | function emitEvent(event) {
FILE: server/api/question/question.socket.js
function register (line 12) | function register(socket) {
function createListener (line 24) | function createListener(event, socket) {
function removeListener (line 30) | function removeListener(event, listener) {
FILE: server/api/thing/thing.controller.js
function respondWithResult (line 15) | function respondWithResult(res, statusCode) {
function saveUpdates (line 24) | function saveUpdates(updates) {
function removeEntity (line 34) | function removeEntity(res) {
function handleEntityNotFound (line 45) | function handleEntityNotFound(res) {
function handleError (line 55) | function handleError(res, statusCode) {
function index (line 63) | function index(req, res) {
function show (line 70) | function show(req, res) {
function create (line 78) | function create(req, res) {
function update (line 85) | function update(req, res) {
function destroy (line 97) | function destroy(req, res) {
FILE: server/api/thing/thing.events.js
function emitEvent (line 26) | function emitEvent(event) {
FILE: server/api/thing/thing.socket.js
function register (line 12) | function register(socket) {
function createListener (line 24) | function createListener(event, socket) {
function removeListener (line 30) | function removeListener(event, listener) {
FILE: server/api/user/index.spec.js
method isAuthenticated (line 15) | isAuthenticated() {
method hasRole (line 18) | hasRole(role) {
method Router (line 33) | Router() {
FILE: server/api/user/user.controller.js
function validationError (line 8) | function validationError(res, statusCode) {
function handleError (line 15) | function handleError(res, statusCode) {
function index (line 26) | function index(req, res) {
function create (line 37) | function create(req, res, next) {
function show (line 54) | function show(req, res, next) {
function destroy (line 71) | function destroy(req, res) {
function changePassword (line 82) | function changePassword(req, res, next) {
function me (line 105) | function me(req, res, next) {
function authCallback (line 121) | function authCallback(req, res, next) {
FILE: server/api/user/user.events.js
function emitEvent (line 26) | function emitEvent(event) {
FILE: server/api/user/user.model.js
method authenticate (line 142) | authenticate(password, callback) {
method makeSalt (line 168) | makeSalt(byteSize, callback) {
method encryptPassword (line 203) | encryptPassword(password, callback) {
FILE: server/app.js
function startServer (line 35) | function startServer() {
FILE: server/auth/auth.service.js
function isAuthenticated (line 18) | function isAuthenticated() {
function hasRole (line 45) | function hasRole(roleRequired) {
function signToken (line 65) | function signToken(id, role) {
function setTokenCookie (line 74) | function setTokenCookie(req, res) {
FILE: server/auth/facebook/passport.js
function setup (line 4) | function setup(User, config) {
FILE: server/auth/google/passport.js
function setup (line 4) | function setup(User, config) {
FILE: server/auth/local/passport.js
function localAuthenticate (line 4) | function localAuthenticate(User, email, password, done) {
function setup (line 28) | function setup(User, config) {
FILE: server/auth/twitter/passport.js
function setup (line 4) | function setup(User, config) {
FILE: server/config/environment/index.js
function requiredProcessEnv (line 6) | function requiredProcessEnv(name) {
FILE: server/config/socketio.js
function onDisconnect (line 9) | function onDisconnect(socket) {
function onConnect (line 13) | function onConnect(socket) {
Condensed preview — 133 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (225K chars).
[
{
"path": ".bowerrc",
"chars": 47,
"preview": "{\n \"directory\": \"client/bower_components\"\n}\n"
},
{
"path": ".buildignore",
"chars": 0,
"preview": ""
},
{
"path": ".editorconfig",
"chars": 415,
"preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
},
{
"path": ".gitattributes",
"chars": 776,
"preview": "* text=auto\n\n# These files are text and should be normalized (Convert crlf => lf)\n*.php text\n*.css text\n*.js text\n*."
},
{
"path": ".gitignore",
"chars": 138,
"preview": "node_modules\npublic\n.tmp\n.sass-cache\n.idea\nclient/bower_components\ndist\n/server/config/local.env.js\nnpm-debug.log\ncovera"
},
{
"path": ".jscsrc",
"chars": 1745,
"preview": "{\n \"excludeFiles\": [\n \"client/app/app.constant.js\"\n ],\n \"esnext\": true,\n \"maximumLineLength\": {\n \"value\": 100,"
},
{
"path": ".travis.yml",
"chars": 211,
"preview": "language: node_js\nnode_js:\n - 4.2.3\nmatrix:\n fast_finish: true\n allow_failures:\n - node_js: 5.1.1\nbefore_script:\n "
},
{
"path": ".yo-rc.json",
"chars": 1720,
"preview": "{\n \"generator-angular-fullstack\": {\n \"generatorVersion\": \"3.3.0\",\n \"endpointDirectory\": \"server/api/\",\n \"inser"
},
{
"path": "Gruntfile.js",
"chars": 20809,
"preview": "// Generated on 2016-03-08 using generator-angular-fullstack 3.3.0\n'use strict';\n\nmodule.exports = function (grunt) {\n "
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Gino, Inc.\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README.md",
"chars": 1528,
"preview": "# PaizaQA\n\nPaizaQA is a Open Source QA service(like StackOverflow) using MEAN stack.\n\nThis project was generated with th"
},
{
"path": "bower.json",
"chars": 997,
"preview": "{\n \"name\": \"paizaqa\",\n \"version\": \"0.0.0\",\n \"dependencies\": {\n \"angular\": \"~1.4.0\",\n \"json3\": \"~3.3.1\",\n \"es"
},
{
"path": "client/.htaccess",
"chars": 24135,
"preview": "# Apache Configuration File\n\n# (!) Using `.htaccess` files slows down Apache, therefore, if you have access\n# to the mai"
},
{
"path": "client/.jshintrc",
"chars": 746,
"preview": "{\n \"node\": true,\n \"browser\": true,\n \"esnext\": true,\n \"bitwise\": true,\n \"camelcase\": true,\n \"curly\": true,\n \"eqeqe"
},
{
"path": "client/app/account/account.js",
"chars": 1306,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .config(function($stateProvider) {\n $stateProvider\n .state('login'"
},
{
"path": "client/app/account/login/login.controller.js",
"chars": 628,
"preview": "'use strict';\n\nclass LoginController {\n constructor(Auth, $state) {\n this.user = {};\n this.errors = {};\n this."
},
{
"path": "client/app/account/login/login.html",
"chars": 1744,
"preview": "<div class=\"container\">\n <div class=\"row\">\n <div class=\"col-sm-12\">\n <h1>Login</h1>\n <p>Accounts are reset"
},
{
"path": "client/app/account/settings/settings.controller.js",
"chars": 659,
"preview": "'use strict';\n\nclass SettingsController {\n constructor(Auth) {\n this.errors = {};\n this.submitted = false;\n\n t"
},
{
"path": "client/app/account/settings/settings.html",
"chars": 1725,
"preview": "<div class=\"container\">\n <div class=\"row\">\n <div class=\"col-sm-12\">\n <h1>Change Password</h1>\n </div>\n <d"
},
{
"path": "client/app/account/signup/signup.controller.js",
"chars": 950,
"preview": "'use strict';\n\nclass SignupController {\n //start-non-standard\n user = {};\n errors = {};\n submitted = false;\n //end-"
},
{
"path": "client/app/account/signup/signup.html",
"chars": 3355,
"preview": "<div class=\"container\">\n <div class=\"row\">\n <div class=\"col-sm-12\">\n <h1>Sign up</h1>\n </div>\n <div class"
},
{
"path": "client/app/admin/admin.controller.js",
"chars": 350,
"preview": "'use strict';\n\n(function() {\n\nclass AdminController {\n constructor(User) {\n // Use the User $resource to fetch all u"
},
{
"path": "client/app/admin/admin.html",
"chars": 488,
"preview": "<div class=\"container\">\n <p>The delete user and user index api routes are restricted to users with the 'admin' role.</p"
},
{
"path": "client/app/admin/admin.module.js",
"chars": 91,
"preview": "'use strict';\n\nangular.module('paizaqaApp.admin', [\n 'paizaqaApp.auth',\n 'ui.router'\n]);\n"
},
{
"path": "client/app/admin/admin.router.js",
"chars": 314,
"preview": "'use strict';\n\nangular.module('paizaqaApp.admin')\n .config(function($stateProvider) {\n $stateProvider\n .state('"
},
{
"path": "client/app/admin/admin.scss",
"chars": 311,
"preview": ".trash { color:rgb(209, 91, 71); }\n\n.user-list {\n\tli {\n\t\tdisplay: flex;\n\t\tborder: none;\n\t\tborder-bottom: 1px lightgray s"
},
{
"path": "client/app/app.constant.js",
"chars": 167,
"preview": "(function(angular, undefined) {\n'use strict';\n\nangular.module('paizaqaApp.constants', [])\n\n.constant('appConfig', {userR"
},
{
"path": "client/app/app.js",
"chars": 465,
"preview": "'use strict';\n\nangular.module('paizaqaApp', [\n 'paizaqaApp.auth',\n 'paizaqaApp.admin',\n 'paizaqaApp.constants',\n 'ng"
},
{
"path": "client/app/app.scss",
"chars": 883,
"preview": "$icon-font-path: \"../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/\";\n@import '../bower_compone"
},
{
"path": "client/app/fromNow/fromNow.filter.js",
"chars": 194,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .filter('fromNow', function () {\n return function (input) {\n retur"
},
{
"path": "client/app/fromNow/fromNow.filter.spec.js",
"chars": 412,
"preview": "'use strict';\n\ndescribe('Filter: fromNow', function () {\n\n // load the filter's module\n beforeEach(module('paizaqaApp'"
},
{
"path": "client/app/questionsCreate/questionsCreate.controller.js",
"chars": 390,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .controller('QuestionsCreateCtrl', function ($scope, $http, $location, Aut"
},
{
"path": "client/app/questionsCreate/questionsCreate.controller.spec.js",
"chars": 484,
"preview": "'use strict';\n\ndescribe('Controller: QuestionsCreateCtrl', function () {\n\n // load the controller's module\n beforeEach"
},
{
"path": "client/app/questionsCreate/questionsCreate.html",
"chars": 1017,
"preview": "<div class=\"container\">\n <form name=\"form\" ng-submit=\"submit()\">\n <h2>Title:</h2>\n <input type=\"text\" class=\"form"
},
{
"path": "client/app/questionsCreate/questionsCreate.js",
"chars": 292,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .config(function ($stateProvider) {\n $stateProvider\n .state('quest"
},
{
"path": "client/app/questionsCreate/questionsCreate.scss",
"chars": 0,
"preview": ""
},
{
"path": "client/app/questionsIndex/questionsIndex.controller.js",
"chars": 1214,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .controller('QuestionsIndexCtrl', function ($scope, $http, $location, Auth"
},
{
"path": "client/app/questionsIndex/questionsIndex.controller.spec.js",
"chars": 498,
"preview": "'use strict';\n\ndescribe('Controller: QuestionsIndexCtrl', function () {\n\n // load the controller's module\n beforeEach("
},
{
"path": "client/app/questionsIndex/questionsIndex.html",
"chars": 2121,
"preview": "<header class=\"hero-unit\" id=\"banner\">\n <div class=\"container\">\n <h1>paizaQA</h1>\n <p class=\"lead\">How to make QA"
},
{
"path": "client/app/questionsIndex/questionsIndex.js",
"chars": 1230,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .config(function ($stateProvider) {\n $stateProvider\n .state('main'"
},
{
"path": "client/app/questionsIndex/questionsIndex.scss",
"chars": 322,
"preview": "#banner {\n border-bottom: none;\n margin-top: -20px;\n}\n\n#banner h1 {\n font-size: 60px;\n line-height: 1;\n l"
},
{
"path": "client/app/questionsShow/questionsShow.controller.js",
"chars": 3373,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .controller('QuestionsShowCtrl', function ($scope, $http, $stateParams, Au"
},
{
"path": "client/app/questionsShow/questionsShow.controller.spec.js",
"chars": 476,
"preview": "'use strict';\n\ndescribe('Controller: QuestionsShowCtrl', function () {\n\n // load the controller's module\n beforeEach(m"
},
{
"path": "client/app/questionsShow/questionsShow.html",
"chars": 7306,
"preview": "<div class=\"container\" id=\"question-show-container\">\n <div>\n <button ng-if=\"isOwner(question)\" type=\"button\" class=\""
},
{
"path": "client/app/questionsShow/questionsShow.js",
"chars": 286,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .config(function ($stateProvider) {\n $stateProvider\n .state('quest"
},
{
"path": "client/app/questionsShow/questionsShow.scss",
"chars": 288,
"preview": "#question-show-container .comment{\n hr {\n margin: 0;\n };\n p {\n margin: 0;\n };\n margin-left: 100px;\n};\n#questi"
},
{
"path": "client/components/auth/auth.module.js",
"chars": 228,
"preview": "'use strict';\n\nangular.module('paizaqaApp.auth', [\n 'paizaqaApp.constants',\n 'paizaqaApp.util',\n 'ngCookies',\n 'ui.r"
},
{
"path": "client/components/auth/auth.service.js",
"chars": 4706,
"preview": "'use strict';\n\n(function() {\n\nfunction AuthService($location, $http, $cookies, $q, appConfig, Util, User) {\n var safeCb"
},
{
"path": "client/components/auth/interceptor.service.js",
"chars": 807,
"preview": "'use strict';\n\n(function() {\n\nfunction authInterceptor($rootScope, $q, $cookies, $injector, Util) {\n var state;\n retur"
},
{
"path": "client/components/auth/router.decorator.js",
"chars": 882,
"preview": "'use strict';\n\n(function() {\n\nangular.module('paizaqaApp.auth')\n .run(function($rootScope, $state, Auth) {\n // Redir"
},
{
"path": "client/components/auth/user.service.js",
"chars": 406,
"preview": "'use strict';\n\n(function() {\n\nfunction UserResource($resource) {\n return $resource('/api/users/:id/:controller', {\n "
},
{
"path": "client/components/footer/footer.directive.js",
"chars": 262,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .directive('footer', function() {\n return {\n templateUrl: 'compone"
},
{
"path": "client/components/footer/footer.html",
"chars": 228,
"preview": "<div class=\"container\">\n <p>Angular Fullstack v3.3.0 |\n <a href=\"https://twitter.com/tyhenkel\">@tyhenkel</a> |\n <"
},
{
"path": "client/components/footer/footer.scss",
"chars": 112,
"preview": "footer.footer {\n text-align: center;\n padding: 30px 0;\n margin-top: 70px;\n border-top: 1px solid #E5E5E5;\n}\n"
},
{
"path": "client/components/modal/modal.html",
"chars": 534,
"preview": "<div class=\"modal-header\">\n <button ng-if=\"modal.dismissable\" type=\"button\" ng-click=\"$dismiss()\" class=\"close\">×"
},
{
"path": "client/components/modal/modal.scss",
"chars": 457,
"preview": ".modal-primary,\n.modal-info,\n.modal-success,\n.modal-warning,\n.modal-danger {\n .modal-header {\n color: #fff;\n bord"
},
{
"path": "client/components/modal/modal.service.js",
"chars": 2348,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .factory('Modal', function($rootScope, $modal) {\n /**\n * Opens a mo"
},
{
"path": "client/components/mongoose-error/mongoose-error.directive.js",
"chars": 356,
"preview": "'use strict';\n\n/**\n * Removes server error when user updates input\n */\nangular.module('paizaqaApp')\n .directive('mongoo"
},
{
"path": "client/components/navbar/navbar.controller.js",
"chars": 919,
"preview": "'use strict';\n\nclass NavbarController {\n //start-non-standard\n\n\n isCollapsed = true;\n //end-non-standard\n\n construct"
},
{
"path": "client/components/navbar/navbar.directive.js",
"chars": 212,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .directive('navbar', () => ({\n templateUrl: 'components/navbar/navbar.h"
},
{
"path": "client/components/navbar/navbar.html",
"chars": 1877,
"preview": "<div class=\"navbar navbar-default navbar-static-top\" ng-controller=\"NavbarController\">\n <div class=\"container\">\n <di"
},
{
"path": "client/components/oauth-buttons/oauth-buttons.controller.js",
"chars": 205,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .controller('OauthButtonsCtrl', function($window) {\n this.loginOauth = "
},
{
"path": "client/components/oauth-buttons/oauth-buttons.controller.spec.js",
"chars": 536,
"preview": "'use strict';\n\ndescribe('Controller: OauthButtonsCtrl', function() {\n\n // load the controller's module\n beforeEach(mod"
},
{
"path": "client/components/oauth-buttons/oauth-buttons.directive.js",
"chars": 318,
"preview": "'use strict';\n\nangular.module('paizaqaApp')\n .directive('oauthButtons', function() {\n return {\n templateUrl: 'c"
},
{
"path": "client/components/oauth-buttons/oauth-buttons.directive.spec.js",
"chars": 1601,
"preview": "'use strict';\n\ndescribe('Directive: oauthButtons', function() {\n\n // load the directive's module and view\n beforeEach("
},
{
"path": "client/components/oauth-buttons/oauth-buttons.html",
"chars": 498,
"preview": "<a ng-class=\"classes\" ng-click=\"OauthButtons.loginOauth('facebook')\" class=\"btn btn-social btn-facebook\">\n <i class=\"fa"
},
{
"path": "client/components/oauth-buttons/oauth-buttons.scss",
"chars": 1,
"preview": "\n"
},
{
"path": "client/components/socket/socket.mock.js",
"chars": 322,
"preview": "'use strict';\n\nangular.module('socketMock', [])\n .factory('socket', function() {\n return {\n socket: {\n c"
},
{
"path": "client/components/socket/socket.service.js",
"chars": 2030,
"preview": "/* global io */\n'use strict';\n\nangular.module('paizaqaApp')\n .factory('socket', function(socketFactory) {\n // socket"
},
{
"path": "client/components/ui-router/ui-router.mock.js",
"chars": 1131,
"preview": "'use strict';\n\nangular.module('stateMock', []);\nangular.module('stateMock').service('$state', function($q) {\n this.ex"
},
{
"path": "client/components/util/util.module.js",
"chars": 54,
"preview": "'use strict';\n\nangular.module('paizaqaApp.util', []);\n"
},
{
"path": "client/components/util/util.service.js",
"chars": 1636,
"preview": "'use strict';\n\n(function() {\n\n/**\n * The Util service is for thin, globally reusable, utility functions\n */\nfunction Uti"
},
{
"path": "client/index.html",
"chars": 6589,
"preview": "<!doctype html>\n<!--[if lt IE 7]> <html class=\"no-js lt-ie9 lt-ie8 lt-ie7\"> <![endif]-->\n<!--[if IE 7]> <ht"
},
{
"path": "client/robots.txt",
"chars": 31,
"preview": "# robotstxt.org\n\nUser-agent: *\n"
},
{
"path": "e2e/account/login/login.po.js",
"chars": 851,
"preview": "/**\n * This file uses the Page Object pattern to define the main page for tests\n * https://docs.google.com/presentation/"
},
{
"path": "e2e/account/login/login.spec.js",
"chars": 2631,
"preview": "'use strict';\n\nvar config = browser.params;\nvar UserModel = require(config.serverConfig.root + '/server/api/user/user.mo"
},
{
"path": "e2e/account/logout/logout.spec.js",
"chars": 1369,
"preview": "'use strict';\n\nvar config = browser.params;\nvar UserModel = require(config.serverConfig.root + '/server/api/user/user.mo"
},
{
"path": "e2e/account/signup/signup.po.js",
"chars": 987,
"preview": "/**\n * This file uses the Page Object pattern to define the main page for tests\n * https://docs.google.com/presentation/"
},
{
"path": "e2e/account/signup/signup.spec.js",
"chars": 2914,
"preview": "'use strict';\n\nvar config = browser.params;\nvar UserModel = require(config.serverConfig.root + '/server/api/user/user.mo"
},
{
"path": "e2e/components/navbar/navbar.po.js",
"chars": 671,
"preview": "/**\n * This file uses the Page Object pattern to define the main page for tests\n * https://docs.google.com/presentation/"
},
{
"path": "e2e/components/oauth-buttons/oauth-buttons.po.js",
"chars": 593,
"preview": "/**\n * This file uses the Page Object pattern to define the main page for tests\n * https://docs.google.com/presentation/"
},
{
"path": "e2e/main/main.po.js",
"chars": 400,
"preview": "/**\n * This file uses the Page Object pattern to define the main page for tests\n * https://docs.google.com/presentation/"
},
{
"path": "e2e/main/main.spec.js",
"chars": 460,
"preview": "'use strict';\n\nvar config = browser.params;\n\ndescribe('Main View', function() {\n var page;\n\n beforeEach(function() {\n "
},
{
"path": "karma.conf.js",
"chars": 3355,
"preview": "// Karma configuration\n// http://karma-runner.github.io/0.10/config/configuration-file.html\n\nmodule.exports = function(c"
},
{
"path": "mocha.conf.js",
"chars": 390,
"preview": "'use strict';\n\n// Register the Babel require hook\nrequire('babel-core/register');\n\nvar chai = require('chai');\n\n// Load "
},
{
"path": "package.json",
"chars": 3439,
"preview": "{\n \"name\": \"paizaqa\",\n \"version\": \"0.0.0\",\n \"main\": \"server/app.js\",\n \"dependencies\": {\n \"babel-runtime\": \"^5.8.2"
},
{
"path": "protractor.conf.js",
"chars": 2374,
"preview": "// Protractor configuration\n// https://github.com/angular/protractor/blob/master/referenceConf.js\n\n'use strict';\n\nvar co"
},
{
"path": "server/.jshintrc",
"chars": 235,
"preview": "{\n \"expr\": true,\n \"node\": true,\n \"esnext\": true,\n \"bitwise\": true,\n \"eqeqeq\": true,\n \"immed\": true,\n \"latedef\": \""
},
{
"path": "server/.jshintrc-spec",
"chars": 252,
"preview": "{\n \"extends\": \".jshintrc\",\n \"globals\": {\n \"jasmine\": true,\n \"describe\": true,\n \"it\": true,\n \"before\": true"
},
{
"path": "server/api/question/index.js",
"chars": 2148,
"preview": "'use strict';\n\nvar express = require('express');\nvar controller = require('./question.controller');\n\nvar router = expres"
},
{
"path": "server/api/question/question.controller.js",
"chars": 10271,
"preview": "/**\n * Using Rails-like standard naming convention for endpoints.\n * GET /api/questions -> index\n * PO"
},
{
"path": "server/api/question/question.events.js",
"chars": 654,
"preview": "/**\n * Question model events\n */\n\n'use strict';\n\nimport {EventEmitter} from 'events';\nvar Question = require('./question"
},
{
"path": "server/api/question/question.integration.js",
"chars": 4377,
"preview": "'use strict';\n\nvar app = require('../..');\nimport request from 'supertest';\nvar User = require('../user/user.model');\n\nv"
},
{
"path": "server/api/question/question.model.js",
"chars": 3159,
"preview": "'use strict';\n\nvar mongoose = require('bluebird').promisifyAll(require('mongoose'));\n\nvar QuestionSchema = new mongoose."
},
{
"path": "server/api/question/question.socket.js",
"chars": 765,
"preview": "/**\n * Broadcast updates to client when the model changes\n */\n\n'use strict';\n\nvar QuestionEvents = require('./question.e"
},
{
"path": "server/api/thing/index.js",
"chars": 388,
"preview": "'use strict';\n\nvar express = require('express');\nvar controller = require('./thing.controller');\n\nvar router = express.R"
},
{
"path": "server/api/thing/index.spec.js",
"chars": 2130,
"preview": "'use strict';\n\nvar proxyquire = require('proxyquire').noPreserveCache();\n\nvar thingCtrlStub = {\n index: 'thingCtrl.inde"
},
{
"path": "server/api/thing/thing.controller.js",
"chars": 2292,
"preview": "/**\n * Using Rails-like standard naming convention for endpoints.\n * GET /api/things -> index\n * POST "
},
{
"path": "server/api/thing/thing.events.js",
"chars": 627,
"preview": "/**\n * Thing model events\n */\n\n'use strict';\n\nimport {EventEmitter} from 'events';\nvar Thing = require('./thing.model');"
},
{
"path": "server/api/thing/thing.integration.js",
"chars": 3286,
"preview": "'use strict';\n\nvar app = require('../..');\nimport request from 'supertest';\n\nvar newThing;\n\ndescribe('Thing API:', funct"
},
{
"path": "server/api/thing/thing.model.js",
"chars": 234,
"preview": "'use strict';\n\nvar mongoose = require('bluebird').promisifyAll(require('mongoose'));\n\nvar ThingSchema = new mongoose.Sch"
},
{
"path": "server/api/thing/thing.socket.js",
"chars": 750,
"preview": "/**\n * Broadcast updates to client when the model changes\n */\n\n'use strict';\n\nvar ThingEvents = require('./thing.events'"
},
{
"path": "server/api/user/index.js",
"chars": 558,
"preview": "'use strict';\n\nimport {Router} from 'express';\nimport * as controller from './user.controller';\nimport * as auth from '."
},
{
"path": "server/api/user/index.spec.js",
"chars": 2588,
"preview": "'use strict';\n\nvar proxyquire = require('proxyquire').noPreserveCache();\n\nvar userCtrlStub = {\n index: 'userCtrl.index'"
},
{
"path": "server/api/user/user.controller.js",
"chars": 2566,
"preview": "'use strict';\n\nimport User from './user.model';\nimport passport from 'passport';\nimport config from '../../config/enviro"
},
{
"path": "server/api/user/user.events.js",
"chars": 615,
"preview": "/**\n * User model events\n */\n\n'use strict';\n\nimport {EventEmitter} from 'events';\nimport User from './user.model';\nvar U"
},
{
"path": "server/api/user/user.integration.js",
"chars": 1475,
"preview": "'use strict';\n\nimport app from '../..';\nimport User from './user.model';\nimport request from 'supertest';\n\ndescribe('Use"
},
{
"path": "server/api/user/user.model.js",
"chars": 4719,
"preview": "'use strict';\n\nimport crypto from 'crypto';\nvar mongoose = require('bluebird').promisifyAll(require('mongoose'));\nimport"
},
{
"path": "server/api/user/user.model.spec.js",
"chars": 1652,
"preview": "'use strict';\n\nimport app from '../..';\nimport User from './user.model';\nvar user;\nvar genUser = function() {\n user = n"
},
{
"path": "server/app.js",
"chars": 1106,
"preview": "/**\n * Main application file\n */\n\n'use strict';\n\nimport express from 'express';\nimport mongoose from 'mongoose';\nmongoos"
},
{
"path": "server/auth/auth.service.js",
"chars": 2114,
"preview": "'use strict';\n\nimport passport from 'passport';\nimport config from '../config/environment';\nimport jwt from 'jsonwebtoke"
},
{
"path": "server/auth/facebook/index.js",
"chars": 465,
"preview": "'use strict';\n\nimport express from 'express';\nimport passport from 'passport';\nimport {setTokenCookie} from '../auth.ser"
},
{
"path": "server/auth/facebook/passport.js",
"chars": 937,
"preview": "import passport from 'passport';\nimport {Strategy as FacebookStrategy} from 'passport-facebook';\n\nexport function setup("
},
{
"path": "server/auth/google/index.js",
"chars": 473,
"preview": "'use strict';\n\nimport express from 'express';\nimport passport from 'passport';\nimport {setTokenCookie} from '../auth.ser"
},
{
"path": "server/auth/google/passport.js",
"chars": 926,
"preview": "import passport from 'passport';\nimport {OAuth2Strategy as GoogleStrategy} from 'passport-google-oauth';\n\nexport functio"
},
{
"path": "server/auth/index.js",
"chars": 632,
"preview": "'use strict';\n\nimport express from 'express';\nimport passport from 'passport';\nimport config from '../config/environment"
},
{
"path": "server/auth/local/index.js",
"chars": 589,
"preview": "'use strict';\n\nimport express from 'express';\nimport passport from 'passport';\nimport {signToken} from '../auth.service'"
},
{
"path": "server/auth/local/passport.js",
"chars": 997,
"preview": "import passport from 'passport';\nimport {Strategy as LocalStrategy} from 'passport-local';\n\nfunction localAuthenticate(U"
},
{
"path": "server/auth/twitter/index.js",
"chars": 424,
"preview": "'use strict';\n\nimport express from 'express';\nimport passport from 'passport';\nimport {setTokenCookie} from '../auth.ser"
},
{
"path": "server/auth/twitter/passport.js",
"chars": 862,
"preview": "import passport from 'passport';\nimport {Strategy as TwitterStrategy} from 'passport-twitter';\n\nexport function setup(Us"
},
{
"path": "server/components/errors/index.js",
"chars": 376,
"preview": "/**\n * Error responses\n */\n\n'use strict';\n\nmodule.exports[404] = function pageNotFound(req, res) {\n var viewFilePath = "
},
{
"path": "server/config/environment/development.js",
"chars": 252,
"preview": "'use strict';\n\n// Development specific configuration\n// ==================================\nmodule.exports = {\n\n // Mong"
},
{
"path": "server/config/environment/index.js",
"chars": 1686,
"preview": "'use strict';\n\nvar path = require('path');\nvar _ = require('lodash');\n\nfunction requiredProcessEnv(name) {\n if (!proces"
},
{
"path": "server/config/environment/production.js",
"chars": 587,
"preview": "'use strict';\n\n// Production specific configuration\n// =================================\nmodule.exports = {\n // Server "
},
{
"path": "server/config/environment/shared.js",
"chars": 111,
"preview": "'use strict';\n\nexports = module.exports = {\n // List of user roles\n userRoles: ['guest', 'user', 'admin']\n};\n"
},
{
"path": "server/config/environment/test.js",
"chars": 355,
"preview": "'use strict';\n\n// Test specific configuration\n// ===========================\nmodule.exports = {\n // MongoDB connection "
},
{
"path": "server/config/express.js",
"chars": 2365,
"preview": "/**\n * Express configuration\n */\n\n'use strict';\n\nimport express from 'express';\nimport favicon from 'serve-favicon';\nimp"
},
{
"path": "server/config/local.env.sample.js",
"chars": 618,
"preview": "'use strict';\n\n// Use local.env.js for environment variables that grunt will set when the server starts locally.\n// Use "
},
{
"path": "server/config/seed.js",
"chars": 1906,
"preview": "/**\n * Populate DB with sample data on server start\n * to disable, edit config/environment/index.js, and set `seedDB: fa"
},
{
"path": "server/config/socketio.js",
"chars": 1670,
"preview": "/**\n * Socket.io configuration\n */\n'use strict';\n\nimport config from './environment';\n\n// When the user disconnects.. pe"
},
{
"path": "server/index.js",
"chars": 327,
"preview": "'use strict';\n\n// Set default node environment to development\nvar env = process.env.NODE_ENV = process.env.NODE_ENV || '"
},
{
"path": "server/routes.js",
"chars": 703,
"preview": "/**\n * Main application routes\n */\n\n'use strict';\n\nimport errors from './components/errors';\nimport path from 'path';\n\ne"
},
{
"path": "server/views/404.html",
"chars": 3529,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <title>Page Not Found :(</title>\n <style>\n "
}
]
About this extraction
This page contains the full source code of the gi-no/paizaqa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 133 files (202.3 KB), approximately 53.8k tokens, and a symbol index with 97 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.