Merge pull request #363 from JocaPC/master

Changed data access component in JSON/NodeJS products app
This commit is contained in:
Jovan Popovic (MSFT)
2018-02-03 19:50:35 +01:00
committed by GitHub
11 changed files with 5733 additions and 131 deletions
@@ -3,4 +3,5 @@ node_modules/*
bin/*.dll
obj/*
*.sln
*.log
*.log
package-lock.json
@@ -1,10 +1,19 @@
var express = require('express');
var config = require('config');
var express = require('express');
var bodyParser = require('body-parser');
var tediousExpress = require('express4-tedious');
var app = express();
app.use(express.static('wwwroot'));
//app.use(bodyParser.json());
app.use(bodyParser.text({ type: 'application/json' }))
app.use(function (req, res, next) {
req.sql = tediousExpress(req, config.get('connection'));
next();
});
app.use('/api/Product', require('./routes/products'));
// catch 404 and forward to error handler
@@ -13,6 +22,7 @@ app.use(function (req, res, next) {
err.status = 404;
next(err);
});
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
@@ -0,0 +1,8 @@
{
"connection":{
"server" : "<<server name or ip>>",
"userName": "<<user name>>",
"password": "<<password>>",
"options": { "encrypt": true, "database": "<<database name>>" }
}
}
@@ -1,85 +0,0 @@
function createConnection() {
var config = {
server : "<SERVER>",
userName: "<USERNAME>",
password: "<PASSWORD>",
// If you're on Azure, you will need this:
options: { encrypt: true, database: 'ProductCatalog' }
};
var Connection = require('tedious').Connection;
var connection = new Connection(config);
return connection;
}
function createRequest(query, connection) {
var Request = require('tedious').Request;
var req =
new Request(query,
function (err, rowCount) {
if (err) {
console.trace(err);
throw err;
}
connection && connection.close();
console.log('Connection closed');
});
return req;
}
function stream (query, connection, output, defaultContent) {
var request = query;
if (typeof query == "string") {
request = createRequest(query, connection);
}
var empty = true;
request.on('row', function (columns) {
if(empty) {
console.log('Response fetched from SQL Database!');
empty = false;
}
output.write(columns[0].value);
});
request.on('done', function (rowCount, more, rows) {
_OnDone(empty, defaultContent, output);
});
request.on('doneProc', function (rowCount, more, rows) {
_OnDone(empty, defaultContent, output);
});
executeRequest (request, connection);
}
function _OnDone(empty, defaultContent, output) {
if(empty) {
output.write(defaultContent);
console.log('No results from database - default content is returned.');
}
try {
console.log('Closing Http Response output.');
output.end();
} catch (err) {
console.error(err);
}
}
function executeRequest (request, connection) {
connection.on('connect', function (err) {
if (err) {
console.trace(err);
throw err;
}
connection.execSql(request);
});
}
module.exports.createConnection = createConnection;
module.exports.createRequest = createRequest;
module.exports.executeRequest = executeRequest;
module.exports.stream = stream;
@@ -11,9 +11,11 @@
"email": "jovanpop@microsoft.com"
},
"dependencies": {
"body-parser": "^1.15.2",
"debug": "^2.2.0",
"express": "^4.14.0",
"tedious": "^1.14.0"
"body-parser": "^1.18.2",
"config": "^1.29.2",
"debug": "^2.6.9",
"express": "^4.16.2",
"express4-tedious": "^0.2.0",
"tedious": "^1.15.0"
}
}
@@ -1,59 +1,41 @@
var express = require('express');
var router = express.Router();
var db = require('../db.js');
var TYPES = require('tedious').TYPES;
/* GET products. */
router.get('/', function (req, res) {
db.stream("select ProductID, Name, Color, Price, Quantity, JSON_VALUE(Data, '$.MadeIn') as MadeIn, JSON_QUERY(Tags) as Tags from Product FOR JSON PATH, ROOT('data')", db.createConnection(), res, '[]');
req.sql("select ProductID, Name, Color, Price, Quantity, JSON_VALUE(Data, '$.MadeIn') as MadeIn, JSON_QUERY(Tags) as Tags from Product FOR JSON PATH, ROOT('data')")
.into(res);
});
/* GET single product. */
router.get('/:id', function (req, res) {
var conn = db.createConnection();
var request = db.createRequest("select ProductID, Name, Color, Price, Quantity, JSON_VALUE(Data, '$.MadeIn') as MadeIn, JSON_QUERY(Tags) as Tags from Product where productid = @id for json path, without_array_wrapper", conn);
request.addParameter('id', TYPES.Int, req.params.id);
db.stream(request, conn, res, '{}');
req.sql("select ProductID, Name, Color, Price, Quantity, JSON_VALUE(Data, '$.MadeIn') as MadeIn, JSON_QUERY(Tags) as Tags from Product where productid = @id for json path, without_array_wrapper")
.param('id', req.params.id, TYPES.Int)
.into(res, '{}');
});
/* POST create product. */
router.post('/', function (req, res) {
var connection = db.createConnection();
var request = db.createRequest("EXEC InsertProductFromJson @json", connection);
request.addParameter('json', TYPES.NVarChar, req.body);
db.executeRequest(request, connection);
res.end();
req.sql("EXEC InsertProductFromJson @json")
.param('json', req.body, TYPES.NVarChar)
.exec(res);
});
/* PUT update product. */
router.put('/:id', function (req, res) {
var connection = db.createConnection();
var request = db.createRequest("EXEC UpdateProductFromJson @id, @json", connection);
request.addParameter('id', TYPES.Int, req.params.id);
request.addParameter('json', TYPES.NVarChar, req.body);
db.executeRequest(request, connection);
res.end();
req.sql("EXEC UpdateProductFromJson @id, @json")
.param('json', req.body, TYPES.NVarChar)
.param('id', req.params.id, TYPES.Int)
.exec(res);
});
/* DELETE delete product. */
router.delete('/:id', function (req, res) {
var connection = db.createConnection();
var request = db.createRequest("DELETE Product WHERE ProductId = @id", connection);
request.addParameter('id', TYPES.Int, req.params.id);
db.executeRequest(request, connection);
res.end();
req.sql("DELETE Product WHERE ProductId = @id")
.param('id', req.params.id, TYPES.Int)
.exec(res);
});
module.exports = router;
@@ -0,0 +1,436 @@
/*!
* QUnit 2.5.0
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2018-01-10T02:56Z
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
/** Header (excluding toolbar) */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699A4;
background-color: #0D3349;
font-size: 1.5em;
line-height: 1em;
font-weight: 400;
border-radius: 5px 5px 0 0;
}
#qunit-header a {
text-decoration: none;
color: #C2CCD1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #FFF;
}
#qunit-banner {
height: 5px;
}
#qunit-filteredTest {
padding: 0.5em 1em 0.5em 1em;
color: #366097;
background-color: #F4FF77;
}
#qunit-userAgent {
padding: 0.5em 1em 0.5em 1em;
color: #FFF;
background-color: #2B81AF;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Toolbar */
#qunit-testrunner-toolbar {
padding: 0.5em 1em 0.5em 1em;
color: #5E740B;
background-color: #EEE;
}
#qunit-testrunner-toolbar .clearfix {
height: 0;
clear: both;
}
#qunit-testrunner-toolbar label {
display: inline-block;
}
#qunit-testrunner-toolbar input[type=checkbox],
#qunit-testrunner-toolbar input[type=radio] {
margin: 3px;
vertical-align: -2px;
}
#qunit-testrunner-toolbar input[type=text] {
box-sizing: border-box;
height: 1.6em;
}
.qunit-url-config,
.qunit-filter,
#qunit-modulefilter {
display: inline-block;
line-height: 2.1em;
}
.qunit-filter,
#qunit-modulefilter {
float: right;
position: relative;
margin-left: 1em;
}
.qunit-url-config label {
margin-right: 0.5em;
}
#qunit-modulefilter-search {
box-sizing: border-box;
width: 400px;
}
#qunit-modulefilter-search-container:after {
position: absolute;
right: 0.3em;
content: "\25bc";
color: black;
}
#qunit-modulefilter-dropdown {
/* align with #qunit-modulefilter-search */
box-sizing: border-box;
width: 400px;
position: absolute;
right: 0;
top: 50%;
margin-top: 0.8em;
border: 1px solid #D3D3D3;
border-top: none;
border-radius: 0 0 .25em .25em;
color: #000;
background-color: #F5F5F5;
z-index: 99;
}
#qunit-modulefilter-dropdown a {
color: inherit;
text-decoration: none;
}
#qunit-modulefilter-dropdown .clickable.checked {
font-weight: bold;
color: #000;
background-color: #D2E0E6;
}
#qunit-modulefilter-dropdown .clickable:hover {
color: #FFF;
background-color: #0D3349;
}
#qunit-modulefilter-actions {
display: block;
overflow: auto;
/* align with #qunit-modulefilter-dropdown-list */
font: smaller/1.5em sans-serif;
}
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
box-sizing: border-box;
max-height: 2.8em;
display: block;
padding: 0.4em;
}
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
float: right;
font: inherit;
}
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
/* insert padding to align with checkbox margins */
padding-left: 3px;
}
#qunit-modulefilter-dropdown-list {
max-height: 200px;
overflow-y: auto;
margin: 0;
border-top: 2px groove threedhighlight;
padding: 0.4em 0 0;
font: smaller/1.5em sans-serif;
}
#qunit-modulefilter-dropdown-list li {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#qunit-modulefilter-dropdown-list .clickable {
display: block;
padding-left: 0.15em;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 1em 0.4em 1em;
border-bottom: 1px solid #FFF;
list-style-position: inside;
}
#qunit-tests > li {
display: none;
}
#qunit-tests li.running,
#qunit-tests li.pass,
#qunit-tests li.fail,
#qunit-tests li.skipped,
#qunit-tests li.aborted {
display: list-item;
}
#qunit-tests.hidepass {
position: relative;
}
#qunit-tests.hidepass li.running,
#qunit-tests.hidepass li.pass:not(.todo) {
visibility: hidden;
position: absolute;
width: 0;
height: 0;
padding: 0;
border: 0;
margin: 0;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li.skipped strong {
cursor: default;
}
#qunit-tests li a {
padding: 0.5em;
color: #C2CCD1;
text-decoration: none;
}
#qunit-tests li p a {
padding: 0.25em;
color: #6B6464;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em;
padding: 0.5em;
background-color: #FFF;
border-radius: 5px;
}
.qunit-source {
margin: 0.6em 0 0.3em;
}
.qunit-collapsed {
display: none;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: 0.2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 0.5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
color: #374E0C;
background-color: #E0F2BE;
text-decoration: none;
}
#qunit-tests ins {
color: #500;
background-color: #FFCACA;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: #000; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
background-color: #FFF;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #3C510C;
background-color: #FFF;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #FFF;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
}
#qunit-tests .fail { color: #000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: #008000; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/*** Aborted tests */
#qunit-tests .aborted { color: #000; background-color: orange; }
/*** Skipped tests */
#qunit-tests .skipped {
background-color: #EBECE9;
}
#qunit-tests .qunit-todo-label,
#qunit-tests .qunit-skipped-label {
background-color: #F4FF77;
display: inline-block;
font-style: normal;
color: #366097;
line-height: 1.8em;
padding: 0 0.5em;
margin: -0.4em 0.4em -0.4em 0;
}
#qunit-tests .qunit-todo-label {
background-color: #EEE;
}
/** Result */
#qunit-testresult {
color: #2B81AF;
background-color: #D2E0E6;
border-bottom: 1px solid #FFF;
}
#qunit-testresult .clearfix {
height: 0;
clear: both;
}
#qunit-testresult .module-name {
font-weight: 700;
}
#qunit-testresult-display {
padding: 0.5em 1em 0.5em 1em;
width: 85%;
float:left;
}
#qunit-testresult-controls {
padding: 0.5em 1em 0.5em 1em;
width: 10%;
float:left;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.5.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="/media/js/jquery.js"></script>
<script src="https://code.jquery.com/qunit/qunit-2.5.0.js"></script>
<script>
QUnit.test( "Product list", assert => {
var done = assert.async();
$.ajax('/api/Product', {dataType: "json"})
.done(response=>{
assert.notEqual( null, response.data, "Return data object in response." );
assert.ok( response.data.length > 10, "Returned " + response.data.length + " products" );
done();
});
});
QUnit.test( "Product details", assert => {
var done = assert.async();
$.ajax('/api/Product/15', {dataType: "json"})
.done(response=>{
assert.notEqual( null, response, "Returned response object." );
assert.equal( null, response.length, "Returned single object." );
assert.equal( 15, response.ProductID, "Returned product with ID 15." );
done();
});
});
QUnit.test( "Create product", assert => {
var done = assert.async();
$.post('/api/Product', {
dataType: "json",
data: { Name:"New Product","Color":"White","Price":75.9900,"Quantity":50,"Tags":["Special offer", "Best-seller"]}})
.done(response=>{
assert.notEqual( null, response, "Returned response object." );
assert.equal( null, response.length, "Returned single object." );
assert.equal( 15, response.ProductID, "Returned product with ID 15." );
done();
});
});
</script>
</body>
</html>
@@ -5,7 +5,7 @@ var tediousExpress = require('express4-tedious');
var app = express();
app.use(function (req, res, next) {
req.query = tediousExpress(req, config.get('connection'));
req.sql = tediousExpress(req, config.get('connection'));
next();
});
@@ -4,7 +4,7 @@ var TYPES = require('tedious').TYPES;
/* GET task listing. */
router.get('/', function (req, res) {
req.query("select * from todo for json path")
req.sql("select * from todo for json path")
.into(res, '[]');
});
@@ -12,7 +12,7 @@ router.get('/', function (req, res) {
/* GET single task. */
router.get('/:id', function (req, res) {
req.query("select * from todo where id = @id for json path, without_array_wrapper")
req.sql("select * from todo where id = @id for json path, without_array_wrapper")
.param('id', req.params.id, TYPES.Int)
.into(res, '{}');
@@ -21,7 +21,7 @@ router.get('/:id', function (req, res) {
/* POST create task. */
router.post('/', function (req, res) {
req.query("exec createTodo @todo")
req.sql("exec createTodo @todo")
.param('todo', req.body, TYPES.NVarChar)
.exec(res);
@@ -30,7 +30,7 @@ router.post('/', function (req, res) {
/* PUT update task. */
router.put('/:id', function (req, res) {
req.query("exec updateTodo @id, @todo")
req.sql("exec updateTodo @id, @todo")
.param('id', req.params.id, TYPES.Int)
.param('todo', req.body, TYPES.NVarChar)
.exec(res);
@@ -40,7 +40,7 @@ router.put('/:id', function (req, res) {
/* DELETE single task. */
router.delete('/:id', function (req, res) {
req.query("delete from todo where id = @id")
req.sql("delete from todo where id = @id")
.param('id', req.params.id, TYPES.Int)
.exec(res);