feat: Update Boilerplate to match Refactored User Stories (#15)

* Initial Refactor

* Updated Tests, Added samples files, removed user stories

* Add `dotenv` package

* Update assertion-analyser.js

Co-authored-by: Shaun Hamilton <51722130+Sky020@users.noreply.github.com>

* Update assertion-analyser.js

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>

* Update assertion-analyser.js

Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>

* Remove unneeded files

* Remove unneeded Code

Co-authored-by: SaintPeter <rex.schrader@gmail.com>
Co-authored-by: Shaun Hamilton <51722130+Sky020@users.noreply.github.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>
This commit is contained in:
Mrugesh Mohapatra 2020-11-19 03:40:43 +05:30 committed by GitHub
parent 46d8807939
commit 1d7e913ee7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 967 additions and 1243 deletions

111
.bashrc
View File

@ -1,111 +0,0 @@
# ~/.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

View File

@ -1,2 +0,0 @@
[core]
excludesfile = /etc/.gitignore-global

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/node_modules/
.env
*.vscode

View File

@ -1,5 +0,0 @@
{"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}

View File

@ -1,17 +0,0 @@
# ~/.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"

View File

@ -1,10 +1,3 @@
**FreeCodeCamp**- Information Security and Quality Assurance
------
[![Run on Repl.it](https://repl.it/badge/github/freeCodeCamp/boilerplate-project-issuetracker)](https://repl.it/github/freeCodeCamp/boilerplate-project-issuetracker)
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 create all of the functional tests in `tests/2_functional-tests.js`
# [Issue Tracker](https://www.freecodecamp.org/learn/quality-assurance/quality-assurance-projects/issue-tracker)

View File

@ -28,18 +28,20 @@
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++ ) {
let openSym = ['[', '{', '"', "'", '('];
let closeSym = [']', '}', '"', "'", ')'];
let type;
let i;
for(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++) {
let open = openSym[type];
let close = closeSym[type];
let count = 1;
let k;
for(k = i+1; k < str.length; k++) {
if(open === '"' || open === "'") {
if(str[k] === close) count--;
if(str[k] === '\\') k++;
@ -50,7 +52,7 @@ function objParser(str, init) {
if(count === 0) break;
}
if(count !== 0) return null;
var obj = str.slice(i, k+1);
let obj = str.slice(i, k+1);
return {
start : i,
end: k,
@ -60,12 +62,13 @@ function objParser(str, init) {
function replacer(str) {
// replace objects with a symbol ( __#n)
var obj;
var cnt = 0;
var data = [];
while(obj = objParser(str)) {
let cnt = 0;
let data = [];
let obj = objParser(str);
while(obj) {
data[cnt] = obj.obj;
str = str.substring(0, obj.start) + '__#' + cnt++ + str.substring(obj.end+1)
str = str.substring(0, obj.start) + '__#' + cnt++ + str.substring(obj.end+1);
obj = objParser(str);
}
return {
str : str,
@ -75,10 +78,10 @@ function replacer(str) {
function splitter(str) {
// split on commas, then restore the objects
var strObj = replacer(str);
var args = strObj.str.split(',');
let strObj = replacer(str);
let args = strObj.str.split(',');
args = args.map(function(a){
var m = a.match(/__#(\d+)/);
let m = a.match(/__#(\d+)/);
while (m) {
a = a.replace(/__#(\d+)/, strObj.dictionary[m[1]]);
m = a.match(/__#(\d+)/);
@ -100,23 +103,29 @@ function assertionAnalyser(body) {
// 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);
let cleanedBody = body.match(/(?:browser\s*\.\s*)?assert\s*\.\s*\w*\([\s\S]*\)/)
if(cleanedBody && Array.isArray(cleanedBody)) {
body = cleanedBody[0];
} else {
// No assertions present
return [];
}
let s = replacer(body);
// split on 'assertion'
var splittedAssertions = s.str.split('assert');
var assertions = splittedAssertions.slice(1);
let splittedAssertions = s.str.split('assert');
let assertions = splittedAssertions.slice(1);
// match the METHODS
var assertionBodies = [];
var methods = assertions.map(function(a, i){
var m = a.match(/^\s*\.\s*(\w+)__#(\d+)/);
let assertionBodies = [];
let methods = assertions.map(function(a, i){
let m = a.match(/^\s*\.\s*(\w+)__#(\d+)/);
assertionBodies.push(parseInt(m[2]));
var pre = splittedAssertions[i].match(/browser\s*\.\s*/) ? 'browser.' : '';
let 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){
let bodies = assertionBodies.map(function(b){
return s.dictionary[b].slice(1,-1).trim();
});
assertions = methods.map(function(m, i) {

1594
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,25 @@
{
"name": "my-hyperdev-app",
"version": "0.0.1",
"description": "What am I about?",
"name": "fcc-issue-tracker",
"version": "1.0.0",
"description": "Issue Tracker Boilerplate",
"main": "server.js",
"scripts": {
"start": "node server.js"
"start": "node server.js",
"test": "\"mocha --timeout 5000 --recursive --exit --ui tdd tests/\""
},
"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"
"body-parser": "^1.19.0",
"chai": "^4.2.0",
"chai-http": "^4.3.0",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"mocha": "^8.1.3",
"mongodb": "^3.6.2"
},
"repository": {
"type": "git",
"url": "https://hyperdev.com/#!/project/welcome-project"
"url": "https://github.com/freeCodeCamp/boilerplate-project-issuetracker"
},
"keywords": [
"node",

View File

@ -8,33 +8,27 @@
'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;
let project = req.params.project;
})
.post(function (req, res){
var project = req.params.project;
let project = req.params.project;
})
.put(function (req, res){
var project = req.params.project;
let project = req.params.project;
})
.delete(function (req, res){
var project = req.params.project;
let project = req.params.project;
});

View File

@ -27,9 +27,9 @@
'use strict';
var cors = require('cors');
var fs = require('fs');
var runner = require('../test-runner');
const cors = require('cors');
const fs = require('fs');
const runner = require('../test-runner');
module.exports = function (app) {
@ -49,16 +49,8 @@ module.exports = function (app) {
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;
let error;
app.get('/_api/get-tests', cors(), function(req, res, next){
console.log(error);
if(!error && process.env.NODE_ENV === 'test') return next();
@ -73,19 +65,10 @@ module.exports = function (app) {
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;
let out;
switch (type) {
case 'unit' :
out = tests.filter(t => t.context.match('Unit Tests'));

3
sample.env Normal file
View File

@ -0,0 +1,3 @@
DB="connection URI here"
PORT=3000
# NODE_ENV=test

View File

@ -1,15 +1,16 @@
'use strict';
var express = require('express');
var bodyParser = require('body-parser');
var expect = require('chai').expect;
var cors = require('cors');
const express = require('express');
const bodyParser = require('body-parser');
const expect = require('chai').expect;
const cors = require('cors');
require('dotenv').config();
var apiRoutes = require('./routes/api.js');
var fccTestingRoutes = require('./routes/fcctesting.js');
var runner = require('./test-runner');
const apiRoutes = require('./routes/api.js');
const fccTestingRoutes = require('./routes/fcctesting.js');
const runner = require('./test-runner');
var app = express();
let app = express();
app.use('/public', express.static(process.cwd() + '/public'));
@ -54,7 +55,7 @@ app.listen(process.env.PORT || 3000, function () {
try {
runner.run();
} catch(e) {
var error = e;
let error = e;
console.log('Tests are not valid:');
console.log(error);
}

View File

@ -25,15 +25,15 @@
*
*/
var analyser = require('./assertion-analyser');
var EventEmitter = require('events').EventEmitter;
const analyser = require('./assertion-analyser');
const EventEmitter = require('events').EventEmitter;
var Mocha = require('mocha'),
const Mocha = require('mocha'),
fs = require('fs'),
path = require('path');
var mocha = new Mocha();
var testDir = './tests'
const mocha = new Mocha();
let testDir = './tests'
// Add each .js file to the mocha instance
@ -47,21 +47,21 @@ fs.readdirSync(testDir).filter(function(file){
);
});
var emitter = new EventEmitter();
let emitter = new EventEmitter();
emitter.run = function() {
var tests = [];
var context = "";
var separator = ' -> ';
let tests = [];
let context = "";
let separator = ' -> ';
// Run the tests.
try {
var runner = mocha.ui('tdd').run()
let runner = mocha.ui('tdd').run()
.on('test end', function(test) {
// remove comments
var body = test.body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
let body = test.body.replace(/\/\/.*\n|\/\*.*\*\//g, '');
// collapse spaces
body = body.replace(/\s+/g,' ');
var obj = {
let obj = {
title: test.title,
context: context.slice(0, -separator.length),
state: test.state,

View File

@ -1,15 +0,0 @@
/*
*
*
* 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 required
});

View File

@ -6,104 +6,127 @@
* (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');
const chaiHttp = require('chai-http');
const chai = require('chai');
const assert = chai.assert;
const server = require('../server');
chai.use(chaiHttp);
suite('Functional Tests', function() {
suite('POST /api/issues/{project} => object with issue data', function() {
suite('POST /api/issues/{project}', 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, Optional Fields Blank', function(done) {
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);
//done();
});
test('Missing required fields => { error: "required field(s) missing" }', function(done) {
//done();
});
});
suite('GET /api/issues/{project}', 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) {
//done();
});
test('Multiple filters (test for multiple fields you know will be in the db for a return)', function(done) {
//done();
});
});
suite('PUT /api/issues/{project}', function() {
//fill me in too!
done();
});
});
test('Required fields filled in', function(done) {
});
test('Missing required fields', function(done) {
});
test('One field to update => {result: "successfully updated", _id: _id}', function(done) {
//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) {
});
test('Multiple fields to update => {result: "successfully updated", _id: _id}', function(done) {
//done();
});
test('No _id submitted => { error: "missing _id" }', function(done) {
//done()
});
test('No fields to update => { error: "no update field(s) sent", _id: _id }', function(done) {
//done()
});
test('Invalid _id => { error: "missing _id" }', function(done) {
//done();
});
});
suite('DELETE /api/issues/{project}', function() {
test('Valid _id', function(done) {
//done();
});
test('Invalid _id => { error: "could not delete", "_id": _id }', function(done) {
const badId = "5f665eb46e296f6b9b6a504d";
//done();
});
test('No _id => { error: "missing _id" }', function(done) {
//done();
});
});
});

View File

@ -1,42 +1,47 @@
<!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">
<title>Issue Tracker</title>
<link rel="icon" type="image/png" href="https://cdn.freecodecamp.org/universal/favicons/favicon-16x16.png" />
<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>
ISQA_4 - Issue Tracker
Issue Tracker
</h1>
</header>
<div id='userstories' style='margin-left: 5%; margin-top: 5%'>
<h3>User Stories</h3>
<ol>
<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>
<section style="margin: 0 50px;">
<h3>Example <code>GET</code> usage</h3>
<code>/api/issues/{project}</code><br>
<code>/api/issues/{project}?open=true&amp;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>Example return</h3>
<pre>
<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>
</pre>
</section>
<hr style='margin: 0 50px'>
<section id='testui' style='margin-left: 50px;'>
<h3>Examples</h3>
Go to <a href='/api/issues/apitest/'><code>/api/issues/apitest/</code></a> project
<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>
@ -54,7 +59,7 @@
<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>
<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>
@ -63,7 +68,7 @@
<button type="submit">Delete Issue</button>
</form>
<code id='jsonResult'></code>
</div>
</section>
<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"

View File

@ -1,9 +1,8 @@
<!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">
<title>Issue Tracker - Project Page</title>
<link rel="icon" type="image/png" href="https://cdn.freecodecamp.org/universal/favicons/favicon-16x16.png" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -37,20 +36,20 @@
crossorigin="anonymous"></script>
<script>
$(function() {
var currentProject = window.location.pathname.replace(/\//g, "");;
var url = "/api/issues/"+currentProject;
let currentProject = window.location.pathname.replace(/\//g, "");;
let url = "/api/issues/"+currentProject;
$('#projectTitle').text('All issues for: '+currentProject)
$.ajax({
type: "GET",
url: url,
success: function(data)
{
var issues= [];
let issues= [];
data.forEach(function(ele) {
console.log(ele);
var openstatus;
let openstatus;
(ele.open) ? openstatus = 'open' : openstatus = 'closed';
var single = [
let single = [
'<div class="issue '+openstatus+'">',
'<p class="id">id: '+ele._id+'</p>',
'<h3>'+ele.issue_title+' - ('+openstatus+')</h3>',
@ -82,7 +81,7 @@
});
$('#issueDisplay').on('click','.closeIssue', function(e) {
var url = "/api/issues/"+currentProject;
let url = "/api/issues/"+currentProject;
$.ajax({
type: "PUT",
url: url,
@ -92,7 +91,7 @@
e.preventDefault();
});
$('#issueDisplay').on('click','.deleteIssue', function(e) {
var url = "/api/issues/"+currentProject;
let url = "/api/issues/"+currentProject;
$.ajax({
type: "DELETE",
url: url,