{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "inputDocument",
"databaseName": "humans",
"collectionName": "humans",
"connectionStringSetting": "humans_DOCUMENTDB",
"direction": "in"
}
],
"disabled": false
}
Last active
April 8, 2020 23:09
-
-
Save dualyticalchemy/1c3ebbe7654c5a31b58bc26e71f1ad0c to your computer and use it in GitHub Desktop.
☁️ Notes on RESTful APIs as Proxified Azure Functions with Cosmos DBs https://medium.com/@dualyticalchemy/notes-on-restful-apis-as-proxified-azure-functions-with-cosmos-dbs-1f73b6573aa2
const _ = require('underscore')
const curry = fn => (...args) => fn.bind(null, ...args)
const join = curry((str, arr) => arr.join(str))
module.exports = async function (context, req) {
let res = {
headers: {}
}
const glue = ""
const sep = "/"
const _proxyUrl = req.query.proxyUrl
const proxyUrl = join(sep)([req.query.proxyUrl, "humans"]);
const originalUrl = _proxyUrl ? req.originalUrl.replace(/https:\/\/anotherfaileddeployment.azurewebsites.net\/api\/getHumansList/g, proxyUrl) : req.originalUrl
let _baseUrl = _proxyUrl ? proxyUrl : "https://anotherfaileddeployment.azurewebsites.net/humans"
let baseUrl = _proxyUrl ? proxyUrl : "https://anotherfaileddeployment.azurewebsites.net/humans"
if (originalUrl.indexOf('page=') !== -1) {
var pageState = parseInt(originalUrl.split('page=').pop(), 10)
baseUrl = join(glue)([ baseUrl, '?page=', pageState ])
}
//context.log(pageState)
//context.log(baseUrl)
const humansList = context.bindings.inputDocument
const query = req.query
const page_size = query.page_size || 10
const page = (query && query.page) ? parseInt(query.page, 10) : 1
const pageMax = page * page_size
const pageMin = (page - 1) * page_size
const _filters = _.mapObject(query, (ref, key) => key.includes("filter") ? { [key.split('filters')[1]]: ref } : null)
const filters = _.filter(_filters, (ref) => ref)
const resultFilter = []
for (let i = 0; i < filters.length; i++) {
resultFilter.push( [
_.keys(filters[i])[0].split('[')[1].split(']')[0],
_.values(filters[i])[0]
] )
}
const _resultFilter = _.object(resultFilter)
const resultFilterHumansList = _.where(humansList, _resultFilter)
const _humansList = _.size(resultFilter) ? resultFilterHumansList : humansList
const filteredHumansList = _humansList.filter((humanRef, index) => {
if (pageMin !== 0)
return (pageMin <= index) && (index < pageMax)
return (index < pageMax);
})
if (!_.size(filteredHumansList)) {
res.headers['Content-Type'] = 'application/json'
res.status = 404
res.body = { error: "List not found" }
context.res = res
return context.done();
}
const output = {
"$filters": _.object(resultFilter),
"$resultsCursorStart": pageMin,
"$resultsCursorEnd": pageMax,
"$pageLength": _.size(filteredHumansList),
"$resultsLength": _.size(_humansList),
"_embedded": { "humans": filteredHumansList }
}
const enableNext = (
_.size(filteredHumansList) === 0 || _.size(filteredHumansList) < (pageMax - pageMin)
) ? false : true
const enablePrev = page > 0 && page !== 1
const selfLinkBase = (pageMin === 0 ? "" : ("?page=" + page))
const prevLinkBase = join(glue)(["?page=", (page - 1)])
const nextLinkBase = join(glue)(["?page=", (page + 1)])
const links = {
"_links": {
"sort": {
"status": "unavailable",
"href": baseUrl.indexOf('page=') !== -1 ? join(glue)([ baseUrl, "&sort=:property&order={asc|desc}" ]) : join(glue)([ _baseUrl, "?sort=:property&order={asc|desc}" ])
},
"filter": {
"status": "available",
"href": baseUrl.indexOf('page=') !== -1 ? join(glue)([ baseUrl, "&filter[:property]=:any" ]) : join(glue)([ _baseUrl, "?filter[:property]=:any" ])
},
"page-size": {
"status": "available",
"href": baseUrl.indexOf('page=') !== -1 ? join(glue)([ baseUrl, "&page_size=:number" ]) : join(glue)([ _baseUrl, "?page_size=:number" ])
},
"prev": {
"status": "available",
"href": join(glue)([ _baseUrl, prevLinkBase ])
},
"self": {
"status": "available",
"href": baseUrl.indexOf('page=') === -1 ? join(glue)([ baseUrl, selfLinkBase ]) : baseUrl
},
"next": {
"status": "available",
"href": join(glue)([ _baseUrl, nextLinkBase ])
}
}
}
if (!enableNext)
delete links._links.next
if (!enablePrev)
delete links._links.prev
let _res = Object.assign(output, links)
res.headers['Content-Type'] = 'application/json';
res.status = 200
res.body = _res
context.res = res;
};
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "outputDocument",
"databaseName": "humans",
"collectionName": "humans",
"createIfNotExists": false,
"connectionStringSetting": "humans_DOCUMENTDB",
"direction": "out"
}
],
"disabled": false
}
module.exports = async function (context, req) {
if (req.body && req.body.name) {
context.bindings.outputDocument = req.body;
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 201, // 201 Created https://httpstatuses.com/201
body: req.body
};
}
else if (req.query.name) {
context.bindings.outputDocument = req.query;
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 201,
body: req.query
};
}
else {
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 400,
body: { error: "The body property 'name' is be required." }
};
}
};
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "inputDocument",
"databaseName": "humans",
"collectionName": "humans",
"connectionStringSetting": "humans_DOCUMENTDB",
"direction": "in"
}
],
"disabled": false
}
const _ = require('underscore')
//const { compose } = require('ramda');
module.exports = async function (context, req) {
context.log(req.params.humanId)
const id = req.query.id || req.params.humanId
if (id) {
//const last = (x) => _.last(x)
//const where = (x) => (x) => _.where(x, { id: id })
//const selectHuman = compose(last, where)
//const selectHuman(context.bindings.inputDocument)
const human = _.last(_.where(context.bindings.inputDocument, { id: id }))
if (!human) {
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 404,
body: "Not found"
};
return;
}
context.res = {
headers: { 'Content-Type': 'application/json' },
body: human
};
}
else {
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 400,
body: "Please pass an ID on the query string"
};
}
};
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"delete"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "cosmosDB",
"name": "outputDocument",
"databaseName": "humans",
"collectionName": "humans",
"createIfNotExists": false,
"connectionStringSetting": "humans_DOCUMENTDB",
"direction": "out"
},
{
"type": "cosmosDB",
"name": "inputDocument",
"databaseName": "humans",
"collectionName": "humans",
"connectionStringSetting": "humans_DOCUMENTDB",
"direction": "in"
}
],
"disabled": false
}
const _ = require('underscore')
module.exports = async function (context, req) {
const id = req.query.id || req.params.humanId
if (id) {
const human = _.last(_.where(context.bindings.inputDocument, { id: id }))
human['deletion'] = "pending"; // Tag the user with the relevant status, use a stored procedure to handle on an interval with a timed function
// set to "deletion/completed" before actually deleting, in case the deletion fails for whatever reason;
// so batch setting the status first and then actually delete anything with "deletion/completed"; the front end
// should always hide elements that have a substring "deletion/*"...
context.bindings.outputDocument = _.without(context.bindings.outputDocument, _.findWhere(context.bindings.outputDocument, {
id: id
}));
context.bindings.outputDocument.push(human)
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 202, // ... Accepted? Usually 200 OK for removal? or 204 No Content? 102 Processing (probably too meta for the context?
body: { id: id, "deletion": "pending" }
};
context.done(null, context.bindings.outputDocument)
}
else {
context.res = {
headers: { 'Content-Type': 'application/json' },
status: 400,
body: { error: "The query string must include an ID." }
};
}
};
function storedProcedure(prefix) {
const collection = getContext().getCollection();
console.log(prefix + ' ' + JSON.stringify(collection));
const string = 'Austin';
const isAccepted = collection.queryDocuments(
collection.getSelfLink(),
`SELECT * FROM humans WHERE CONTAINS(humans.location, '${string}')`,
function (err, feed, options) {
if (err) throw err;
if (!feed || !feed.length) {
const response = getContext().getResponse();
response.setBody('no docs found');
} else {
const response = getContext().getResponse();
const body = { prefix: prefix, feed: feed[0] };
response.setBody(JSON.stringify(body));
}
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
{
"$schema": "http://json.schemastore.org/proxies",
"proxies": {
"humans": {
"matchCondition": {
"route": "humans/{humanId}",
"methods": [
"GET"
]
},
"backendUri": "https://anotherfaileddeployment.azurewebsites.net/api/getHumanById?code=SEVnr4zmTvzx76xS3VZNkysN2ovSQbaV2Mwtl/exTVPUOVeIcUXwpA=="
},
"humansAll": {
"matchCondition": {
"route": "humans",
"methods": [
"GET"
]
},
"backendUri": "https://anotherfaileddeployment.azurewebsites.net/api/getHumansList?code=K0MtifNQ79ATooUjDO7VdeVInCFIz8VXAoDxXlarO3QBBuK3IjzNDg=="
},
"humansCreate": {
"matchCondition": {
"route": "humans",
"methods": [
"POST"
]
},
"backendUri": "https://anotherfaileddeployment.azurewebsites.net/api/createHuman?code=8RMypv5p9oiMhYJrVnaCj4v4/u/49AAzY8fcWF3PhMoaWUdzE7WGGg=="
},
"humansRemove": {
"matchCondition": {
"route": "humans/{humanId}",
"methods": [
"DELETE"
]
},
"backendUri": "https://anotherfaileddeployment.azurewebsites.net/api/removeHumanById?code=1c14mHBfa7CmdsVNGgOd75A0OwMIjXajsHB5wpPFq5HJceek1DiLQg=="
}
}
}
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var proxy = require('express-http-proxy'); | |
var app = require('express')(); | |
app.use(function (req, res, next) { | |
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:1234'); | |
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); | |
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); | |
res.setHeader('Access-Control-Allow-Credentials', true); | |
next(); | |
}); | |
app.use('/', proxy('https://anotherfaileddeployment.azurewebsites.net')); | |
app.listen('9091', () => { | |
console.log('Proxy on http://localhost:9091') | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment