Initial commit from Gomix.
This commit is contained in:
		
							parent
							
								
									3603695e3f
								
							
						
					
					
						commit
						50b0188343
					
				
							
								
								
									
										111
									
								
								.bashrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								.bashrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					# ~/.bashrc: executed by bash(1) for non-login shells.
 | 
				
			||||||
 | 
					# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
 | 
				
			||||||
 | 
					# for examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If not running interactively, don't do anything
 | 
				
			||||||
 | 
					case $- in
 | 
				
			||||||
 | 
					    *i*) ;;
 | 
				
			||||||
 | 
					      *) return;;
 | 
				
			||||||
 | 
					esac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# don't put duplicate lines or lines starting with space in the history.
 | 
				
			||||||
 | 
					# See bash(1) for more options
 | 
				
			||||||
 | 
					HISTCONTROL=ignoreboth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# append to the history file, don't overwrite it
 | 
				
			||||||
 | 
					shopt -s histappend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
 | 
				
			||||||
 | 
					HISTSIZE=1000
 | 
				
			||||||
 | 
					HISTFILESIZE=2000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# check the window size after each command and, if necessary,
 | 
				
			||||||
 | 
					# update the values of LINES and COLUMNS.
 | 
				
			||||||
 | 
					shopt -s checkwinsize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If set, the pattern "**" used in a pathname expansion context will
 | 
				
			||||||
 | 
					# match all files and zero or more directories and subdirectories.
 | 
				
			||||||
 | 
					#shopt -s globstar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# make less more friendly for non-text input files, see lesspipe(1)
 | 
				
			||||||
 | 
					[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# set variable identifying the chroot you work in (used in the prompt below)
 | 
				
			||||||
 | 
					if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
 | 
				
			||||||
 | 
					    debian_chroot=$(cat /etc/debian_chroot)
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# set a fancy prompt (non-color, unless we know we "want" color)
 | 
				
			||||||
 | 
					case "$TERM" in
 | 
				
			||||||
 | 
					    xterm-color|*-256color) color_prompt=yes;;
 | 
				
			||||||
 | 
					esac
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# uncomment for a colored prompt, if the terminal has the capability; turned
 | 
				
			||||||
 | 
					# off by default to not distract the user: the focus in a terminal window
 | 
				
			||||||
 | 
					# should be on the output of commands, not on the prompt
 | 
				
			||||||
 | 
					#force_color_prompt=yes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -n "$force_color_prompt" ]; then
 | 
				
			||||||
 | 
					    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
 | 
				
			||||||
 | 
					        # We have color support; assume it's compliant with Ecma-48
 | 
				
			||||||
 | 
					        # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
 | 
				
			||||||
 | 
					        # a case would tend to support setf rather than setaf.)
 | 
				
			||||||
 | 
					        color_prompt=yes
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        color_prompt=
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					if [ "$color_prompt" = yes ]; then
 | 
				
			||||||
 | 
					    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@${PROJECT_NAME}\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    PS1='${debian_chroot:+($debian_chroot)}\u@${PROJECT_NAME}:\w\$ '
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					unset color_prompt force_color_prompt
 | 
				
			||||||
 | 
					# If this is an xterm set the title to user@host:dir
 | 
				
			||||||
 | 
					case "$TERM" in
 | 
				
			||||||
 | 
					xterm*|rxvt*)
 | 
				
			||||||
 | 
					    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
 | 
				
			||||||
 | 
					    ;;
 | 
				
			||||||
 | 
					*)
 | 
				
			||||||
 | 
					    ;;
 | 
				
			||||||
 | 
					esac
 | 
				
			||||||
 | 
					# enable color support of ls and also add handy aliases
 | 
				
			||||||
 | 
					if [ -x /usr/bin/dircolors ]; then
 | 
				
			||||||
 | 
					    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
 | 
				
			||||||
 | 
					    alias ls='ls --color=auto'
 | 
				
			||||||
 | 
					    #alias dir='dir --color=auto'
 | 
				
			||||||
 | 
					    #alias vdir='vdir --color=auto'
 | 
				
			||||||
 | 
					    alias grep='grep --color=auto'
 | 
				
			||||||
 | 
					    alias fgrep='fgrep --color=auto'
 | 
				
			||||||
 | 
					    alias egrep='egrep --color=auto'
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					# colored GCC warnings and errors
 | 
				
			||||||
 | 
					#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
 | 
				
			||||||
 | 
					# some more ls aliases
 | 
				
			||||||
 | 
					alias ll='ls -alF'
 | 
				
			||||||
 | 
					alias la='ls -A'
 | 
				
			||||||
 | 
					alias l='ls -CF'
 | 
				
			||||||
 | 
					# Add an "alert" alias for long running commands.  Use like so:
 | 
				
			||||||
 | 
					#   sleep 10; alert
 | 
				
			||||||
 | 
					alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s
 | 
				
			||||||
 | 
					/[;&|]\s*alert$//'\'')"'
 | 
				
			||||||
 | 
					# Alias definitions.
 | 
				
			||||||
 | 
					# You may want to put all your additions into a separate file like
 | 
				
			||||||
 | 
					# ~/.bash_aliases, instead of adding them here directly.
 | 
				
			||||||
 | 
					# See /usr/share/doc/bash-doc/examples in the bash-doc package.
 | 
				
			||||||
 | 
					if [ -f ~/.bash_aliases ]; then
 | 
				
			||||||
 | 
					    . ~/.bash_aliases
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					# enable programmable completion features (you don't need to enable
 | 
				
			||||||
 | 
					# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
 | 
				
			||||||
 | 
					# sources /etc/bash.bashrc).
 | 
				
			||||||
 | 
					if ! shopt -oq posix; then
 | 
				
			||||||
 | 
					  if [ -f /usr/share/bash-completion/bash_completion ]; then
 | 
				
			||||||
 | 
					    . /usr/share/bash-completion/bash_completion
 | 
				
			||||||
 | 
					  elif [ -f /etc/bash_completion ]; then
 | 
				
			||||||
 | 
					    . /etc/bash_completion
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export NVM_DIR="/home/nvm/.nvm"
 | 
				
			||||||
 | 
					[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitconfig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					[core]
 | 
				
			||||||
 | 
						excludesfile = /etc/.gitignore-global
 | 
				
			||||||
							
								
								
									
										5
									
								
								.hyperdev-assets
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.hyperdev-assets
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{"name":"drag-in-files.svg","date":"2016-10-22T16:17:49.954Z","url":"https://cdn.hyperdev.com/drag-in-files.svg","type":"image/svg","size":7646,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/drag-in-files.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(102, 153, 205)","uuid":"adSBq97hhhpFNUna"}
 | 
				
			||||||
 | 
					{"name":"click-me.svg","date":"2016-10-23T16:17:49.954Z","url":"https://cdn.hyperdev.com/click-me.svg","type":"image/svg","size":7116,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/click-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(243, 185, 186)","uuid":"adSBq97hhhpFNUnb"}
 | 
				
			||||||
 | 
					{"name":"paste-me.svg","date":"2016-10-24T16:17:49.954Z","url":"https://cdn.hyperdev.com/paste-me.svg","type":"image/svg","size":7242,"imageWidth":276,"imageHeight":276,"thumbnail":"https://cdn.hyperdev.com/paste-me.svg","thumbnailWidth":276,"thumbnailHeight":276,"dominantColor":"rgb(42, 179, 185)","uuid":"adSBq97hhhpFNUnc"}
 | 
				
			||||||
 | 
					{"name":"Screen Shot 2016-12-16 at 1.35.56 AM.png","date":"2016-12-16T06:36:32.934Z","url":"https://cdn.gomix.com/d7932c52-287f-4dae-b175-631fef453000%2FScreen%20Shot%202016-12-16%20at%201.35.56%20AM.png","type":"image/png","size":18223,"imageWidth":499,"imageHeight":80,"thumbnail":"https://cdn.gomix.com/d7932c52-287f-4dae-b175-631fef453000%2Fthumbnails%2FScreen%20Shot%202016-12-16%20at%201.35.56%20AM.png","thumbnailWidth":330,"thumbnailHeight":53,"dominantColor":"rgb(236,236,236)","uuid":"IHJDL7lzupnoDep4"}
 | 
				
			||||||
 | 
					{"uuid":"IHJDL7lzupnoDep4","deleted":true}
 | 
				
			||||||
							
								
								
									
										17
									
								
								.profile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.profile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					# ~/.profile: executed by the command interpreter for login shells.
 | 
				
			||||||
 | 
					# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
 | 
				
			||||||
 | 
					# exists.
 | 
				
			||||||
 | 
					# see /usr/share/doc/bash/examples/startup-files for examples.
 | 
				
			||||||
 | 
					# the files are located in the bash-doc package.
 | 
				
			||||||
 | 
					# the default umask is set in /etc/profile; for setting the umask
 | 
				
			||||||
 | 
					# for ssh logins, install and configure the libpam-umask package.
 | 
				
			||||||
 | 
					#umask 022
 | 
				
			||||||
 | 
					# if running bash
 | 
				
			||||||
 | 
					if [ -n "$BASH_VERSION" ]; then
 | 
				
			||||||
 | 
					    # include .bashrc if it exists
 | 
				
			||||||
 | 
					    if [ -f "$HOME/.bashrc" ]; then
 | 
				
			||||||
 | 
					        . "$HOME/.bashrc"
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					# set PATH so it includes user's private bin directories
 | 
				
			||||||
 | 
					PATH="$HOME/bin:$HOME/.local/bin:$HOME/node_modules/.bin:$PATH"
 | 
				
			||||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							@ -1,2 +1,11 @@
 | 
				
			|||||||
# boilerplate-project-issuetracker
 | 
					**FreeCodeCamp**- Information Security and Quality Assurance
 | 
				
			||||||
A boilerplate for a freeCodeCamp project.
 | 
					------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Project Issue Tracker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1) SET NODE_ENV to `test` without quotes and set DB to your mongo connection string in .env file
 | 
				
			||||||
 | 
					2) Complete the project in `routes/api.js` or by creating a handler/controller
 | 
				
			||||||
 | 
					3) You will add any security features to `server.js`
 | 
				
			||||||
 | 
					4) You will create all of the functional tests in `tests/2_functional-tests.js`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										131
									
								
								assertion-analyser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								assertion-analyser.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*       DO NOT EDIT THIS FILE
 | 
				
			||||||
 | 
					*       For FCC testing purposes!
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function objParser(str, init) {
 | 
				
			||||||
 | 
					  // finds objects, arrays, strings, and function arguments
 | 
				
			||||||
 | 
					  // between parens, because they may contain ','
 | 
				
			||||||
 | 
					  var openSym = ['[', '{', '"', "'", '('];
 | 
				
			||||||
 | 
					  var closeSym = [']', '}', '"', "'", ')'];
 | 
				
			||||||
 | 
					  var type;
 | 
				
			||||||
 | 
					  for(var i = (init || 0); i < str.length; i++ ) {
 | 
				
			||||||
 | 
					    type = openSym.indexOf(str[i]);
 | 
				
			||||||
 | 
					    if( type !== -1)  break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (type === -1) return null;
 | 
				
			||||||
 | 
					  var open = openSym[type];
 | 
				
			||||||
 | 
					  var close = closeSym[type];
 | 
				
			||||||
 | 
					  var count = 1;
 | 
				
			||||||
 | 
					  for(var k = i+1; k < str.length; k++) {
 | 
				
			||||||
 | 
					    if(open === '"' || open === "'") {
 | 
				
			||||||
 | 
					      if(str[k] === close) count--;
 | 
				
			||||||
 | 
					      if(str[k] === '\\') k++;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if(str[k] === open) count++;
 | 
				
			||||||
 | 
					      if(str[k] === close) count--;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if(count === 0) break;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if(count !== 0) return null;
 | 
				
			||||||
 | 
					  var obj = str.slice(i, k+1);
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    start : i,
 | 
				
			||||||
 | 
					    end: k,
 | 
				
			||||||
 | 
					    obj: obj
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function replacer(str) {
 | 
				
			||||||
 | 
					  // replace objects with a symbol ( __#n)
 | 
				
			||||||
 | 
					  var obj;
 | 
				
			||||||
 | 
					  var cnt = 0;
 | 
				
			||||||
 | 
					  var data = [];
 | 
				
			||||||
 | 
					  while(obj = objParser(str)) {
 | 
				
			||||||
 | 
					    data[cnt] = obj.obj;
 | 
				
			||||||
 | 
					    str = str.substring(0, obj.start) + '__#' + cnt++ + str.substring(obj.end+1)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    str : str,
 | 
				
			||||||
 | 
					    dictionary : data
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function splitter(str) {
 | 
				
			||||||
 | 
					  // split on commas, then restore the objects
 | 
				
			||||||
 | 
					  var strObj = replacer(str);
 | 
				
			||||||
 | 
					  var args = strObj.str.split(',');
 | 
				
			||||||
 | 
					  args = args.map(function(a){
 | 
				
			||||||
 | 
					    var m = a.match(/__#(\d+)/);
 | 
				
			||||||
 | 
					    while (m) {
 | 
				
			||||||
 | 
					      a = a.replace(/__#(\d+)/, strObj.dictionary[m[1]]);
 | 
				
			||||||
 | 
					      m = a.match(/__#(\d+)/);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return a.trim();
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  return args;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function assertionAnalyser(body) {
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // already filtered in the test runner
 | 
				
			||||||
 | 
					  // // remove comments
 | 
				
			||||||
 | 
					  // body = body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
 | 
				
			||||||
 | 
					  // // get test function body
 | 
				
			||||||
 | 
					  // body = body.match(/\{\s*([\s\S]*)\}\s*$/)[1];
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if(!body) return "invalid assertion";
 | 
				
			||||||
 | 
					  // replace assertions bodies, so that they cannot
 | 
				
			||||||
 | 
					  // contain the word 'assertion'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var body = body.match(/(?:browser\s*\.\s*)?assert\s*\.\s*\w*\([\s\S]*\)/)[0];
 | 
				
			||||||
 | 
					  var s = replacer(body);
 | 
				
			||||||
 | 
					  // split on 'assertion'
 | 
				
			||||||
 | 
					  var splittedAssertions = s.str.split('assert');
 | 
				
			||||||
 | 
					  var assertions = splittedAssertions.slice(1);
 | 
				
			||||||
 | 
					  // match the METHODS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var assertionBodies = [];
 | 
				
			||||||
 | 
					  var methods = assertions.map(function(a, i){
 | 
				
			||||||
 | 
					    var m = a.match(/^\s*\.\s*(\w+)__#(\d+)/);
 | 
				
			||||||
 | 
					    assertionBodies.push(parseInt(m[2]));
 | 
				
			||||||
 | 
					    var pre = splittedAssertions[i].match(/browser\s*\.\s*/) ? 'browser.' : '';
 | 
				
			||||||
 | 
					    return pre + m[1];
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  if(methods.some(function(m){ return !m })) return "invalid assertion";
 | 
				
			||||||
 | 
					  // remove parens from the assertions bodies
 | 
				
			||||||
 | 
					  var bodies = assertionBodies.map(function(b){
 | 
				
			||||||
 | 
					    return s.dictionary[b].slice(1,-1).trim();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  assertions = methods.map(function(m, i) {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      method: m,
 | 
				
			||||||
 | 
					      args: splitter(bodies[i]) //replace objects, split on ',' ,then restore objects
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  return assertions;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = assertionAnalyser;
 | 
				
			||||||
							
								
								
									
										33
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"name": "my-hyperdev-app",
 | 
				
			||||||
 | 
						"version": "0.0.1",
 | 
				
			||||||
 | 
						"description": "What am I about?",
 | 
				
			||||||
 | 
						"main": "server.js",
 | 
				
			||||||
 | 
						"scripts": {
 | 
				
			||||||
 | 
							"start": "node server.js"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"dependencies": {
 | 
				
			||||||
 | 
							"express": "^4.14.0",
 | 
				
			||||||
 | 
							"cors": "^2.8.1",
 | 
				
			||||||
 | 
							"body-parser": "^1.15.2",
 | 
				
			||||||
 | 
							"chai": "^3.5.0",
 | 
				
			||||||
 | 
							"mongodb": "^2.2.16",
 | 
				
			||||||
 | 
							"chai-http": "^3.0.0",
 | 
				
			||||||
 | 
							"mocha": "^3.2.0",
 | 
				
			||||||
 | 
							"zombie": "^5.0.5",
 | 
				
			||||||
 | 
							"helmet": "^3.1.0"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"engines": {
 | 
				
			||||||
 | 
							"node": "4.4.3"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"repository": {
 | 
				
			||||||
 | 
							"type": "git",
 | 
				
			||||||
 | 
							"url": "https://hyperdev.com/#!/project/welcome-project"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"keywords": [
 | 
				
			||||||
 | 
							"node",
 | 
				
			||||||
 | 
							"hyperdev",
 | 
				
			||||||
 | 
							"express"
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						"license": "MIT"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								public/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								public/style.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					* {
 | 
				
			||||||
 | 
					  margin: 0px;
 | 
				
			||||||
 | 
					  padding: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					  padding: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.issue {
 | 
				
			||||||
 | 
					  margin: 20px;
 | 
				
			||||||
 | 
					  padding: 5px;
 | 
				
			||||||
 | 
					  width: 60%;
 | 
				
			||||||
 | 
					  border: 2px solid black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.closed {
 | 
				
			||||||
 | 
					  background-color: lightgrey;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.id {
 | 
				
			||||||
 | 
					  font-size: 10.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								routes/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								routes/api.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*       Complete the API routing below
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var expect = require('chai').expect;
 | 
				
			||||||
 | 
					var MongoClient = require('mongodb');
 | 
				
			||||||
 | 
					var ObjectId = require('mongodb').ObjectID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CONNECTION_STRING = process.env.DB; //MongoClient.connect(CONNECTION_STRING, function(err, db) {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function (app) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app.route('/api/issues/:project')
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    .get(function (req, res){
 | 
				
			||||||
 | 
					      var project = req.params.project;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .post(function (req, res){
 | 
				
			||||||
 | 
					      var project = req.params.project;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .put(function (req, res){
 | 
				
			||||||
 | 
					      var project = req.params.project;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .delete(function (req, res){
 | 
				
			||||||
 | 
					      var project = req.params.project;
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										103
									
								
								routes/fcctesting.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								routes/fcctesting.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*       DO NOT EDIT THIS FILE
 | 
				
			||||||
 | 
					*       For FCC testing purposes!
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var cors = require('cors');
 | 
				
			||||||
 | 
					var fs = require('fs');
 | 
				
			||||||
 | 
					var runner = require('../test-runner');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function (app) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app.route('/_api/server.js')
 | 
				
			||||||
 | 
					    .get(function(req, res, next) {
 | 
				
			||||||
 | 
					      console.log('requested');
 | 
				
			||||||
 | 
					      fs.readFile(__dirname + '/server.js', function(err, data) {
 | 
				
			||||||
 | 
					        if(err) return next(err);
 | 
				
			||||||
 | 
					        res.send(data.toString());
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  app.route('/_api/routes/api.js')
 | 
				
			||||||
 | 
					    .get(function(req, res, next) {
 | 
				
			||||||
 | 
					      console.log('requested');
 | 
				
			||||||
 | 
					      fs.readFile(__dirname + '/routes/api.js', function(err, data) {
 | 
				
			||||||
 | 
					        if(err) return next(err);
 | 
				
			||||||
 | 
					        res.type('txt').send(data.toString());
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  app.route('/_api/controllers/convertHandler.js')
 | 
				
			||||||
 | 
					    .get(function(req, res, next) {
 | 
				
			||||||
 | 
					      console.log('requested');
 | 
				
			||||||
 | 
					      fs.readFile(__dirname + '/controllers/convertHandler.js', function(err, data) {
 | 
				
			||||||
 | 
					        if(err) return next(err);
 | 
				
			||||||
 | 
					        res.type('txt').send(data.toString());
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  var error;
 | 
				
			||||||
 | 
					  app.get('/_api/get-tests', cors(), function(req, res, next){
 | 
				
			||||||
 | 
					    console.log(error);
 | 
				
			||||||
 | 
					    if(!error && process.env.NODE_ENV === 'test') return next();
 | 
				
			||||||
 | 
					    res.json({status: 'unavailable'});
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function(req, res, next){
 | 
				
			||||||
 | 
					    if(!runner.report) return next();
 | 
				
			||||||
 | 
					    res.json(testFilter(runner.report, req.query.type, req.query.n));
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function(req, res){
 | 
				
			||||||
 | 
					    runner.on('done', function(report){
 | 
				
			||||||
 | 
					      process.nextTick(() =>  res.json(testFilter(runner.report, req.query.type, req.query.n)));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  app.get('/_api/app-info', function(req, res) {
 | 
				
			||||||
 | 
					    var hs = Object.keys(res._headers)
 | 
				
			||||||
 | 
					      .filter(h => !h.match(/^access-control-\w+/));
 | 
				
			||||||
 | 
					    var hObj = {};
 | 
				
			||||||
 | 
					    hs.forEach(h => {hObj[h] = res._headers[h]});
 | 
				
			||||||
 | 
					    delete res._headers['strict-transport-security'];
 | 
				
			||||||
 | 
					    res.json({headers: hObj});
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function testFilter(tests, type, n) {
 | 
				
			||||||
 | 
					  var out;
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case 'unit' :
 | 
				
			||||||
 | 
					      out = tests.filter(t => t.context.match('Unit Tests'));
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case 'functional':
 | 
				
			||||||
 | 
					      out = tests.filter(t => t.context.match('Functional Tests') && !t.title.match('#example'));
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      out = tests;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if(n !== undefined) {
 | 
				
			||||||
 | 
					    return out[n] || out;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return out;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										65
									
								
								server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								server.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var express     = require('express');
 | 
				
			||||||
 | 
					var bodyParser  = require('body-parser');
 | 
				
			||||||
 | 
					var expect      = require('chai').expect;
 | 
				
			||||||
 | 
					var cors        = require('cors');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var apiRoutes         = require('./routes/api.js');
 | 
				
			||||||
 | 
					var fccTestingRoutes  = require('./routes/fcctesting.js');
 | 
				
			||||||
 | 
					var runner            = require('./test-runner');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use('/public', express.static(process.cwd() + '/public'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use(cors({origin: '*'})); //For FCC testing purposes only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use(bodyParser.json());
 | 
				
			||||||
 | 
					app.use(bodyParser.urlencoded({ extended: true }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Sample front-end
 | 
				
			||||||
 | 
					app.route('/:project/')
 | 
				
			||||||
 | 
					  .get(function (req, res) {
 | 
				
			||||||
 | 
					    res.sendFile(process.cwd() + '/views/issue.html');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Index page (static HTML)
 | 
				
			||||||
 | 
					app.route('/')
 | 
				
			||||||
 | 
					  .get(function (req, res) {
 | 
				
			||||||
 | 
					    res.sendFile(process.cwd() + '/views/index.html');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//For FCC testing purposes
 | 
				
			||||||
 | 
					fccTestingRoutes(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Routing for API 
 | 
				
			||||||
 | 
					apiRoutes(app);  
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					//404 Not Found Middleware
 | 
				
			||||||
 | 
					app.use(function(req, res, next) {
 | 
				
			||||||
 | 
					  res.status(404)
 | 
				
			||||||
 | 
					    .type('text')
 | 
				
			||||||
 | 
					    .send('Not Found');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Start our server and tests!
 | 
				
			||||||
 | 
					app.listen(process.env.PORT || 3000, function () {
 | 
				
			||||||
 | 
					  console.log("Listening on port " + process.env.PORT);
 | 
				
			||||||
 | 
					  if(process.env.NODE_ENV==='test') {
 | 
				
			||||||
 | 
					    console.log('Running Tests...');
 | 
				
			||||||
 | 
					    setTimeout(function () {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        runner.run();
 | 
				
			||||||
 | 
					      } catch(e) {
 | 
				
			||||||
 | 
					        var error = e;
 | 
				
			||||||
 | 
					          console.log('Tests are not valid:');
 | 
				
			||||||
 | 
					          console.log(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, 3500);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = app; //for testing
 | 
				
			||||||
							
								
								
									
										106
									
								
								test-runner.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								test-runner.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*       DO NOT EDIT THIS FILE
 | 
				
			||||||
 | 
					*       For FCC testing purposes!
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var analyser = require('./assertion-analyser');
 | 
				
			||||||
 | 
					var EventEmitter = require('events').EventEmitter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Mocha = require('mocha'),
 | 
				
			||||||
 | 
					    fs = require('fs'),
 | 
				
			||||||
 | 
					    path = require('path');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var mocha = new Mocha();
 | 
				
			||||||
 | 
					var testDir = './tests'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Add each .js file to the mocha instance
 | 
				
			||||||
 | 
					fs.readdirSync(testDir).filter(function(file){
 | 
				
			||||||
 | 
					    // Only keep the .js files
 | 
				
			||||||
 | 
					    return file.substr(-3) === '.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}).forEach(function(file){
 | 
				
			||||||
 | 
					    mocha.addFile(
 | 
				
			||||||
 | 
					        path.join(testDir, file)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var emitter = new EventEmitter();  
 | 
				
			||||||
 | 
					emitter.run = function() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var tests = [];
 | 
				
			||||||
 | 
					  var context = "";
 | 
				
			||||||
 | 
					  var separator = ' -> ';
 | 
				
			||||||
 | 
					  // Run the tests.
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					  var runner = mocha.ui('tdd').run()
 | 
				
			||||||
 | 
					    .on('test end', function(test) {
 | 
				
			||||||
 | 
					        // remove comments
 | 
				
			||||||
 | 
					        var body = test.body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
 | 
				
			||||||
 | 
					        // collapse spaces
 | 
				
			||||||
 | 
					        body = body.replace(/\s+/g,' ');
 | 
				
			||||||
 | 
					        var obj = {
 | 
				
			||||||
 | 
					          title: test.title,
 | 
				
			||||||
 | 
					          context: context.slice(0, -separator.length),
 | 
				
			||||||
 | 
					          state: test.state,
 | 
				
			||||||
 | 
					          // body: body,
 | 
				
			||||||
 | 
					          assertions: analyser(body)
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        tests.push(obj);
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .on('end', function() {
 | 
				
			||||||
 | 
					        emitter.report = tests;
 | 
				
			||||||
 | 
					        emitter.emit('done', tests)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .on('suite', function(s) {
 | 
				
			||||||
 | 
					      context += (s.title + separator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .on('suite end', function(s) {
 | 
				
			||||||
 | 
					      context = context.slice(0, -(s.title.length + separator.length))
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  } catch(e) {
 | 
				
			||||||
 | 
					    throw(e);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = emitter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Mocha.runner Events:
 | 
				
			||||||
 | 
					 * can be used to build a better custom report
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *   - `start`  execution started
 | 
				
			||||||
 | 
					 *   - `end`  execution complete
 | 
				
			||||||
 | 
					 *   - `suite`  (suite) test suite execution started
 | 
				
			||||||
 | 
					 *   - `suite end`  (suite) all tests (and sub-suites) have finished
 | 
				
			||||||
 | 
					 *   - `test`  (test) test execution started
 | 
				
			||||||
 | 
					 *   - `test end`  (test) test completed
 | 
				
			||||||
 | 
					 *   - `hook`  (hook) hook execution started
 | 
				
			||||||
 | 
					 *   - `hook end`  (hook) hook complete
 | 
				
			||||||
 | 
					 *   - `pass`  (test) test passed
 | 
				
			||||||
 | 
					 *   - `fail`  (test, err) test failed
 | 
				
			||||||
 | 
					 *   - `pending`  (test) test pending
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
							
								
								
									
										15
									
								
								tests/1_unit-tests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/1_unit-tests.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*       FILL IN EACH UNIT TEST BELOW COMPLETELY
 | 
				
			||||||
 | 
					*       -----[Keep the tests in the same order!]----
 | 
				
			||||||
 | 
					*       (if additional are added, keep them at the very end!)
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var chai = require('chai');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suite('Unit Tests', function(){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//none requiered
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										109
									
								
								tests/2_functional-tests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								tests/2_functional-tests.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					*       FILL IN EACH FUNCTIONAL TEST BELOW COMPLETELY
 | 
				
			||||||
 | 
					*       -----[Keep the tests in the same order!]-----
 | 
				
			||||||
 | 
					*       (if additional are added, keep them at the very end!)
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var chaiHttp = require('chai-http');
 | 
				
			||||||
 | 
					var chai = require('chai');
 | 
				
			||||||
 | 
					var assert = chai.assert;
 | 
				
			||||||
 | 
					var server = require('../server');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					chai.use(chaiHttp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suite('Functional Tests', function() {
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    suite('POST /api/issues/{project} => object with issue data', function() {
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('Every field filled in', function(done) {
 | 
				
			||||||
 | 
					       chai.request(server)
 | 
				
			||||||
 | 
					        .post('/api/issues/test')
 | 
				
			||||||
 | 
					        .send({
 | 
				
			||||||
 | 
					          issue_title: 'Title',
 | 
				
			||||||
 | 
					          issue_text: 'text',
 | 
				
			||||||
 | 
					          created_by: 'Functional Test - Every field filled in',
 | 
				
			||||||
 | 
					          assigned_to: 'Chai and Mocha',
 | 
				
			||||||
 | 
					          status_text: 'In QA'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .end(function(err, res){
 | 
				
			||||||
 | 
					          assert.equal(res.status, 200);
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          //fill me in too!
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('Required fields filled in', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('Missing required fields', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    suite('PUT /api/issues/{project} => text', function() {
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('No body', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('One field to update', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('Multiple fields to update', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    suite('GET /api/issues/{project} => Array of objects with issue data', function() {
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('No filter', function(done) {
 | 
				
			||||||
 | 
					        chai.request(server)
 | 
				
			||||||
 | 
					        .get('/api/issues/test')
 | 
				
			||||||
 | 
					        .query({})
 | 
				
			||||||
 | 
					        .end(function(err, res){
 | 
				
			||||||
 | 
					          assert.equal(res.status, 200);
 | 
				
			||||||
 | 
					          assert.isArray(res.body);
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'issue_title');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'issue_text');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'created_on');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'updated_on');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'created_by');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'assigned_to');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'open');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], 'status_text');
 | 
				
			||||||
 | 
					          assert.property(res.body[0], '_id');
 | 
				
			||||||
 | 
					          done();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('One filter', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('Multiple filters (test for multiple fields you know will be in the db for a return)', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    suite('DELETE /api/issues/{project} => text', function() {
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('No _id', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      test('Valid _id', function(done) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										111
									
								
								views/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								views/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <title>Welcome to HyperDev!</title>
 | 
				
			||||||
 | 
					    <meta name="description" content="A cool thing made with HyperDev">
 | 
				
			||||||
 | 
					    <link id="favicon" rel="icon" href="https://hyperdev.com/favicon-app.ico" type="image/x-icon">
 | 
				
			||||||
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					      <h1>
 | 
				
			||||||
 | 
					        ISQA_4 - Issue Tracker
 | 
				
			||||||
 | 
					      </h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <div id='userstories' style='margin-left: 5%; margin-top: 5%'>
 | 
				
			||||||
 | 
					      <h3>User Stories</h3>
 | 
				
			||||||
 | 
					      <ol>
 | 
				
			||||||
 | 
					        <li>Prevent cross site scripting(XSS attack).</li>
 | 
				
			||||||
 | 
					        <li>I can <b>POST</b> <code>/api/issues/{projectname}</code> with form data containing required <i>issue_title</i>, <i>issue_text</i>, <i>created_by</i>, and optional <i>assigned_to</i> and <i>status_text</i>.</li>
 | 
				
			||||||
 | 
					        <li>The object saved (and returned) will include all of those fields (blank for optional no input) and also include <i>created_on</i>(date/time), <i>updated_on</i>(date/time), <i>open</i>(boolean, true for open, false for closed), and <i>_id</i>.</li>
 | 
				
			||||||
 | 
					        <li>I can <b>PUT</b> <code>/api/issues/{projectname}</code> with a <i>_id</i> and any fields in the object with a value to object said object. Returned will be 'successfully updated' or 'could not update '+_id. This should always update <i>updated_on</i>. If no fields are sent return 'no updated field sent'.</li>
 | 
				
			||||||
 | 
					        <li>I can <b>DELETE</b> <code>/api/issues/{projectname}</code> with a <i>_id</i> to completely delete an issue. If no _id is sent return '_id error', success: 'deleted '+_id, failed: 'could not delete '+_id.</li>
 | 
				
			||||||
 | 
					        <li>I can <b>GET</b> <code>/api/issues/{projectname}</code> for an array of all issues on that specific project with all the information for each issue as was returned when posted.</li>
 | 
				
			||||||
 | 
					        <li>I can filter my get request by also passing along any field and value in the query(ie. <code>/api/issues/{project}?open=false</code>). I can pass along as many fields/values as I want.</li>
 | 
				
			||||||
 | 
					        <li>All 11 functional tests are complete and passing.</li>
 | 
				
			||||||
 | 
					      </ol>
 | 
				
			||||||
 | 
					      <br>
 | 
				
			||||||
 | 
					      <h3>Example get usage:</h3>
 | 
				
			||||||
 | 
					      <code>/api/issues/{project}</code><br>
 | 
				
			||||||
 | 
					      <code>/api/issues/{project}?open=true&assigned_to=Joe</code><br>
 | 
				
			||||||
 | 
					      <h3>Example return:</h3>
 | 
				
			||||||
 | 
					      <code>[{"_id":"5871dda29faedc3491ff93bb","issue_title":"Fix error in posting data","issue_text":"When we post data it has an error.","created_on":"2017-01-08T06:35:14.240Z","updated_on":"2017-01-08T06:35:14.240Z","created_by":"Joe","assigned_to":"Joe","open":true,"status_text":"In QA"},...]</code>
 | 
				
			||||||
 | 
					      <br><br>
 | 
				
			||||||
 | 
					      <h2><a href='/apitest/'>EXAMPLE: Go to <i>/apitest/</i> project issues</a></h2>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <hr style='margin: 50px'>
 | 
				
			||||||
 | 
					    <div id='testui' style='margin-left: 5%'>
 | 
				
			||||||
 | 
					      <h2 style="text-align: left">API Tests:</h2>
 | 
				
			||||||
 | 
					      <h3>Submit issue on <i>apitest</i></h3>
 | 
				
			||||||
 | 
					      <form id="testForm" class="border">
 | 
				
			||||||
 | 
					        <input type="text" name="issue_title" placeholder="*Title" style="width: 100px" required=''><br>
 | 
				
			||||||
 | 
					        <textarea type="text" name="issue_text" placeholder="*Text" style="width: 100px" required=''></textarea><br>
 | 
				
			||||||
 | 
					        <input type="text" name="created_by" placeholder="*Created by" style="width: 100px" required=''><br>
 | 
				
			||||||
 | 
					        <input type="text" name="assigned_to" placeholder="(opt)Assigned to" style="width: 100px"><br>
 | 
				
			||||||
 | 
					        <input type="text" name="status_text" placeholder="(opt)Status text" style="width: 100px"><br>
 | 
				
			||||||
 | 
					        <button type="submit">Submit Issue</button>
 | 
				
			||||||
 | 
					      </form><br>
 | 
				
			||||||
 | 
					      <h3>Update issue on <i>apitest</i> (Change any or all to update issue on the _id supplied)</h3>
 | 
				
			||||||
 | 
					      <form id="testForm2" class="border">
 | 
				
			||||||
 | 
					        <input type="text" name="_id" placeholder="*_id" style="width: 100px" required=''><br>
 | 
				
			||||||
 | 
					        <input type="text" name="issue_title" placeholder="(opt)Title" style="width: 100px"><br>
 | 
				
			||||||
 | 
					        <textarea type="text" name="issue_text" placeholder="(opt)Text" style="width: 100px"></textarea><br>
 | 
				
			||||||
 | 
					        <input type="text" name="created_by" placeholder="(opt)Created by" style="width: 100px"><br>
 | 
				
			||||||
 | 
					        <input type="text" name="assigned_to" placeholder="(opt)Assigned to" style="width: 100px"><br>
 | 
				
			||||||
 | 
					        <input type="text" name="status_text" placeholder="(opt)Status text" style="width: 100px"><br>
 | 
				
			||||||
 | 
					        <label><input type="checkbox" name="open" value=false> Check to close issue</label><br>
 | 
				
			||||||
 | 
					        <button type="submit">Submit Issue</button>
 | 
				
			||||||
 | 
					      </form><br>
 | 
				
			||||||
 | 
					      <h3>Delete issue on <i>apitest</i></h3>
 | 
				
			||||||
 | 
					      <form id="testForm2" class="border">
 | 
				
			||||||
 | 
					        <input type="text" name="_id" placeholder="_id" style="width: 100px" required=''><br>
 | 
				
			||||||
 | 
					        <button type="submit">Delete Issue</button>
 | 
				
			||||||
 | 
					      </form>
 | 
				
			||||||
 | 
					      <code id='jsonResult'></code>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <hr style='margin: 50px; margin-top: 200px'>
 | 
				
			||||||
 | 
					    <!-- Your web-app is https, so your scripts need to be too -->
 | 
				
			||||||
 | 
					    <script src="https://code.jquery.com/jquery-2.2.1.min.js"
 | 
				
			||||||
 | 
					            integrity="sha256-gvQgAFzTH6trSrAWoH1iPo9Xc96QxSZ3feW6kem+O00="
 | 
				
			||||||
 | 
					            crossorigin="anonymous"></script>
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					      $(function() {
 | 
				
			||||||
 | 
					        $('#testForm').submit(function(e) {
 | 
				
			||||||
 | 
					          $.ajax({
 | 
				
			||||||
 | 
					            url: '/api/issues/apitest',
 | 
				
			||||||
 | 
					            type: 'post',
 | 
				
			||||||
 | 
					            data: $('#testForm').serialize(),
 | 
				
			||||||
 | 
					            success: function(data) {
 | 
				
			||||||
 | 
					              $('#jsonResult').text(JSON.stringify(data));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        $('#testForm2').submit(function(e) {
 | 
				
			||||||
 | 
					          $.ajax({
 | 
				
			||||||
 | 
					            url: '/api/issues/apitest',
 | 
				
			||||||
 | 
					            type: 'put',
 | 
				
			||||||
 | 
					            data: $('#testForm2').serialize(),
 | 
				
			||||||
 | 
					            success: function(data) {
 | 
				
			||||||
 | 
					              $('#jsonResult').text(JSON.stringify(data));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        $('#testForm3').submit(function(e) {
 | 
				
			||||||
 | 
					          $.ajax({
 | 
				
			||||||
 | 
					            url: '/api/issues/apitest',
 | 
				
			||||||
 | 
					            type: 'delete',
 | 
				
			||||||
 | 
					            data: $('#testForm3').serialize(),
 | 
				
			||||||
 | 
					            success: function(data) {
 | 
				
			||||||
 | 
					              $('#jsonResult').text(JSON.stringify(data));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					   </script>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										107
									
								
								views/issue.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								views/issue.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <title>Welcome to HyperDev!</title>
 | 
				
			||||||
 | 
					    <meta name="description" content="A cool thing made with HyperDev">
 | 
				
			||||||
 | 
					    <link id="favicon" rel="icon" href="https://hyperdev.com/favicon-app.ico" type="image/x-icon">
 | 
				
			||||||
 | 
					    <meta charset="utf-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="/public/style.css">
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <header>
 | 
				
			||||||
 | 
					      <h1 id='projectTitle'></h1>
 | 
				
			||||||
 | 
					    </header>
 | 
				
			||||||
 | 
					    <center>
 | 
				
			||||||
 | 
					      <div id='submitNewIssue'>
 | 
				
			||||||
 | 
					        <br>
 | 
				
			||||||
 | 
					        <h3>Submit a new issue:</h3>
 | 
				
			||||||
 | 
					        <form id="newIssue" method="post" action="/api/">
 | 
				
			||||||
 | 
					          <input type="text" name="issue_title" placeholder="*Title" style="width: 320px; margin-bottom: 3px;" required=''><br>
 | 
				
			||||||
 | 
					          <textarea type="text" name="issue_text" placeholder="*Text" style="width: 320px; height: 100px;" required=''></textarea><br>
 | 
				
			||||||
 | 
					          <input type="text" name="created_by" placeholder="*Created by" style="width: 100px" required=''>
 | 
				
			||||||
 | 
					          <input type="text" name="assigned_to" placeholder="(opt)Assigned to" style="width: 100px">
 | 
				
			||||||
 | 
					          <input type="text" name="status_text" placeholder="(opt)Status text" style="width: 100px"><br>
 | 
				
			||||||
 | 
					          <button type="submit">Submit Issue</button>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      <div id='issueDisplay'></div>
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      <hr style='margin: 50px; margin-top: 200px'>
 | 
				
			||||||
 | 
					    </center>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    <script src="https://code.jquery.com/jquery-2.2.1.min.js"
 | 
				
			||||||
 | 
					            integrity="sha256-gvQgAFzTH6trSrAWoH1iPo9Xc96QxSZ3feW6kem+O00="
 | 
				
			||||||
 | 
					            crossorigin="anonymous"></script>
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					      $(function() {
 | 
				
			||||||
 | 
					        var currentProject = window.location.pathname.replace(/\//g, "");;
 | 
				
			||||||
 | 
					        var url = "/api/issues/"+currentProject;
 | 
				
			||||||
 | 
					        $('#projectTitle').text('All issues for: '+currentProject)
 | 
				
			||||||
 | 
					        $.ajax({
 | 
				
			||||||
 | 
					          type: "GET",
 | 
				
			||||||
 | 
					          url: url,
 | 
				
			||||||
 | 
					          success: function(data)
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            var issues= [];
 | 
				
			||||||
 | 
					            data.forEach(function(ele) {
 | 
				
			||||||
 | 
					              console.log(ele);
 | 
				
			||||||
 | 
					              var openstatus;
 | 
				
			||||||
 | 
					              (ele.open) ? openstatus = 'open' : openstatus = 'closed';
 | 
				
			||||||
 | 
					              var single = [
 | 
				
			||||||
 | 
					                '<div class="issue '+openstatus+'">',
 | 
				
			||||||
 | 
					                '<p class="id">id: '+ele._id+'</p>',
 | 
				
			||||||
 | 
					                '<h3>'+ele.issue_title+' -  ('+openstatus+')</h3>',
 | 
				
			||||||
 | 
					                '<br>',
 | 
				
			||||||
 | 
					                '<p>'+ele.issue_text+'</p>',
 | 
				
			||||||
 | 
					                '<p>'+ele.status_text+'</p>',
 | 
				
			||||||
 | 
					                '<br>',
 | 
				
			||||||
 | 
					                '<p class="id"><b>Created by:</b> '+ele.created_by+'  <b>Assigned to:</b> '+ele.assigned_to,
 | 
				
			||||||
 | 
					                '<p class="id"><b>Created on:</b> '+ele.created_on+'  <b>Last updated:</b> '+ele.updated_on,
 | 
				
			||||||
 | 
					                '<br><a href="#" class="closeIssue" id="'+ele._id+'">close?</a> <a href="#" class="deleteIssue" id="'+ele._id+'">delete?</a>',
 | 
				
			||||||
 | 
					                '</div>'
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					              ];
 | 
				
			||||||
 | 
					              issues.push(single.join(''));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            $('#issueDisplay').html(issues.join(''));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        $('#newIssue').submit(function(e){
 | 
				
			||||||
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					          $(this).attr('action', "/api/issues/" + currentProject);
 | 
				
			||||||
 | 
					          $.ajax({
 | 
				
			||||||
 | 
					            type: "POST",
 | 
				
			||||||
 | 
					            url: url,
 | 
				
			||||||
 | 
					            data: $(this).serialize(),
 | 
				
			||||||
 | 
					            success: function(data) { window.location.reload(true); }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        $('#issueDisplay').on('click','.closeIssue', function(e) {
 | 
				
			||||||
 | 
					          var url = "/api/issues/"+currentProject;
 | 
				
			||||||
 | 
					          $.ajax({
 | 
				
			||||||
 | 
					            type: "PUT",
 | 
				
			||||||
 | 
					            url: url,
 | 
				
			||||||
 | 
					            data: {_id: $(this).attr('id'), open: false},
 | 
				
			||||||
 | 
					            success: function(data) { alert(data); window.location.reload(true); }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        $('#issueDisplay').on('click','.deleteIssue', function(e) {
 | 
				
			||||||
 | 
					          var url = "/api/issues/"+currentProject;
 | 
				
			||||||
 | 
					          $.ajax({
 | 
				
			||||||
 | 
					            type: "DELETE",
 | 
				
			||||||
 | 
					            url: url,
 | 
				
			||||||
 | 
					            data: {_id: $(this).attr('id')},
 | 
				
			||||||
 | 
					            success: function(data) { alert(data); window.location.reload(true); }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          e.preventDefault();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					   </script>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user