Skip to content

Instantly share code, notes, and snippets.

@avoidwork
Last active August 31, 2021 19:12
Show Gist options
  • Save avoidwork/ac4024bf90cbadabaaff45ecbc2a18a8 to your computer and use it in GitHub Desktop.
Save avoidwork/ac4024bf90cbadabaaff45ecbc2a18a8 to your computer and use it in GitHub Desktop.
Azure Function node.js with pagination, sorting & hypermedia
'use strict';
const url = require('url'),
config = require('./config.json'),
mongodb = require('mongodb'),
keysort = require('keysort');
function clone (arg) {
return JSON.parse(JSON.stringify(arg));
}
function connect (uri) {
return new Promise((resolve, reject) => {
mongodb.connect(uri, (err, arg) => {
if (err) {
reject(err);
} else {
resolve(arg);
}
});
});
}
function find (db, arg) {
return new Promise((resolve, reject) => {
const coll = db.collection(arg);
coll.find({}).toArray((err, args) => {
if (err !== null) {
reject(err);
} else {
resolve(args.map(i => {
const o = clone(i);
delete o._id; // Azure serializer breaks on this key/value
return o;
}));
}
});
});
}
function keys (query) {
const result = {};
query.split('&').forEach(i => {
const split = i.split('=');
if (split.length > 0 && split[0] !== '') {
result[split[0]] = decodeURIComponent(split[1] || '');
}
});
return result;
}
function pages (uri, links, page, page_size, total) {
const nth = Math.ceil(total / page_size);
if (nth > 1) {
if (page > 1) {
links.push({uri: uri.replace('page=0', 'page=1'), rel: 'first'});
}
if (page - 1 > 1 && page <= nth) {
links.push({uri: uri.replace('page=0', `page=${(page - 1)}`), rel: 'prev'});
}
if (page + 1 < nth) {
links.push({uri: uri.replace('page=0', `page=${(page + 1)}`), rel: 'next'});
}
if (nth > 0 && page !== nth) {
links.push({uri: uri.replace('page=0', `page=${nth}`), rel: 'last'});
}
}
}
function where (data = [], predicate = {}) {
const keys = Object.keys(predicate),
signature = keys.length === 0 ? 'return true;' : 'return (' + Object.keys(predicate).map(i => {
const arg = typeof predicate[i] === 'string' ? '"' + predicate[i] + '"' : predicate[i];
return 'Array.isArray(a["' + i + '"]) ? a["' + i + '"].includes(' + arg + ') : a["' + i + '"] === ' + arg;
}).join(') && (') + ');',
fn = new Function('a', signature);
return data.filter(fn);
}
function transform (context, data = [], status = 200) {
try {
const parsed = url.parse(context.req.originalUrl),
links = [{rel: 'collection', uri: '/api'}],
query = keys(parsed.query || ''),
sort = query.sort || '',
page = isNaN(query.page) === false ? Number(query.page) : 1,
page_size = isNaN(query.page_size) === false ? Number(query.page_size) : 5,
start = (page - 1) * page_size,
end = (start > -1 ? start : 0) + page_size;
if (query.page_size === void 0) {
query.page_size = page_size;
}
const uri = `${parsed.pathname}?${Object.keys(query).filter(i => i !== 'page').map(i => `${i}=${encodeURIComponent(query[i])}`).join('&')}&page=0`;
// Reducing `query` to a predicate
delete query.sort;
delete query.page;
delete query.page_size;
data = where(data, query);
pages(uri, links, page, page_size, data.length);
context.res = {
status: status,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=1800',
Links: links.map(i => `<${i.uri}>; rel="${i.rel}"`).join(', ')
},
body: {
data: (sort !== '' ? keysort(data, sort) : data).slice(start, end),
error: null,
links: links,
status: status
}
};
} catch (err) {
context.res = {
status: 500,
headers: {
'Content-Type': 'application/json'
},
body: {
data: null,
error: err.stack || err.message || err,
links: [],
status: 500
}
};
}
}
const fn = (context = {req: {originalUrl: '/api/articles?sort=Date%20desc&Solution=Campaign'}, res: {}}) => {
let client;
connect(config.cosmos.uri).then(arg => {
client = arg;
})
.then(() => find(client.db(config.cosmos.database), config.cosmos.collection))
.then(data => transform(context, data))
.then(() => {
client.close();
client = void 0;
context.done();
})
.catch(err => {
context.log(err.stack || err.message || err);
if (client !== void 0) {
try {
client.close();
} catch (e) {
context.log(e.stack || e.message || e);
}
}
});
};
module.exports = fn;
//fn(); // Enable for local dev
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment