Skip to content

Commit

Permalink
feat(hooks): add basics (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
JacopoDaeli committed Mar 14, 2023
1 parent 33bc5ea commit dbc8543
Show file tree
Hide file tree
Showing 8 changed files with 3,734 additions and 3,353 deletions.
26 changes: 26 additions & 0 deletions bin/dynamo-tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,32 @@ const tables = {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5
}
},
[`${NAMESPACE}-hooks`]: {
KeySchema: [
{
AttributeName: 'keyname',
KeyType: 'HASH'
},
{
AttributeName: 'id',
KeyType: 'RANGE'
}
],
AttributeDefinitions: [
{
AttributeName: 'keyname',
AttributeType: 'S'
},
{
AttributeName: 'id',
AttributeType: 'S'
}
],
ProvisionedThroughput: {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5
}
}
};

Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function cleanEnvAliasRecord(record) {

module.exports = fp(
/**
* Initialize Object plugin
* Initialize Env plugin
*
* @param {WarehouseApp} fastify Fastify instance
* @returns {Promise<void>} Promise representing plugin initialization result
Expand Down
130 changes: 130 additions & 0 deletions lib/plugins/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const fp = require('fastify-plugin');
const { v4: uuidv4 } = require('uuid');

const HOOKS_TABLE = 'warehouse-hooks';

/**
* @typedef {import('../warehouse').WarehouseApp} WarehouseApp
*/

/**
* Return a clean Hook record
*
* @param {Object} record Hook record
* @returns {Object} JSON object
*/
function cleanHookRecord(record) {
const { id, keyname: name, url } = record;
return { id, name, url };
}

module.exports = fp(
/**
* Initialize Hook plugin
*
* @param {WarehouseApp} fastify Fastify instance
* @returns {Promise<void>} Promise representing plugin initialization result
*/
async function (fastify) {
const { dynamo } = fastify;

fastify.decorate(
'getHooks',
/**
* Get an Object hooks
*
* @param {Object} opts Method parameters
* @param {string} opts.name Object name
* @returns {Promise<Object>} The object
*/
async function getHooks({ name: keyname }) {
const { Items: hooks } = await dynamo
.query({
KeyConditionExpression: 'keyname = :name',
ExpressionAttributeValues: {
':name': keyname
},
TableName: HOOKS_TABLE
})
.promise();
return hooks.map((hook) => cleanHookRecord(hook));
}
);

fastify.decorate(
'getHook',
/**
* Get an Object hook
*
* @param {Object} opts Method parameters
* @param {string} opts.name Object name
* @param {string} opts.id Hook id
* @returns {Promise<Object>} The hook
*/
async function getHook({ name: keyname, id }) {
const params = {
Key: { keyname, id },
TableName: HOOKS_TABLE
};
const { Item: item } = await dynamo.get(params).promise();
if (!item) return null;
return cleanHookRecord(item);
}
);

fastify.decorate(
'createHook',
/**
* Create hook for object
*
* @param {Object} opts Method parameters
* @param {string} opts.name Object name
* @param {string} opts.url Hook URL
* @returns {Promise<string>} The hook id
*/
async function createHook({ name: keyname, url }) {
const id = uuidv4();

await dynamo
.put({
Item: {
keyname,
id,
url
},
TableName: HOOKS_TABLE
})
.promise();

return id;
}
);

fastify.decorate(
'deleteHook',
/**
* Delete an Object hook
*
* @param {Object} opts Method parameters
* @param {string} opts.name Object name
* @param {string} opts.id Hook id
* @returns {Promise<void>} The hook
*/
async function deleteHook({ name: keyname, id }) {
const params = {
Key: { keyname, id },
TableName: HOOKS_TABLE
};
return dynamo.delete(params).promise();
}
);
},
{
fastify: '3.x',
name: 'hook',
decorators: {
fastify: ['dynamo']
},
dependencies: ['aws']
}
);
219 changes: 219 additions & 0 deletions lib/routes/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/**
* @typedef {import('fastify').FastifyRequest} FastifyRequest
* @typedef {import('fastify').FastifyReply} FastifyReply
* @typedef {import('../warehouse').WarehouseApp} WarehouseApp
*/

/**
* HTTP Request Handler
*
* @callback FastifyHandler
* @param {FastifyRequest} req Fastify request object
* @param {FastifyReply} res Fastify response object
*/

module.exports =
/**
* Initialize hook routes
*
* @param {WarehouseApp} fastify Warehouse app instance
* @returns {Promise<void>} Promise representing routes initialization result
*/
async function (fastify) {
fastify.route({
method: 'GET',
url: '/objects/:name/hooks',
preHandler: fastify.auth([fastify.verifyAuthentication]),
schema: {
params: {
type: 'object',
properties: {
name: { type: 'string' }
}
},
response: {
200: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
name: { type: 'string' },
url: { type: 'string', format: 'uri' }
}
}
}
}
},
handler:
/**
* @type {FastifyHandler}
*/
// eslint-disable-next-line no-unused-vars
async (req, res) => {
const {
params: { name }
} = req;

return fastify.getHooks({ name });
}
});

fastify.route({
method: 'GET',
url: '/objects/:name/hooks/:id',
preHandler: fastify.auth([fastify.verifyAuthentication]),
schema: {
params: {
type: 'object',
properties: {
name: { type: 'string' },
id: { type: 'string', format: 'uuid' }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' },
name: { type: 'string' },
url: { type: 'string', format: 'uri' }
}
}
}
},
handler:
/**
* @type {FastifyHandler}
*/
// eslint-disable-next-line no-unused-vars
async (req, res) => {
const {
params: { name, id }
} = req;

const hook = await fastify.getHook({ name, id });
if (!hook) {
throw fastify.httpErrors.notFound(
`Hook '${id}' not found for object '${name}'`
);
}

return hook;
}
});

fastify.route({
method: 'POST',
url: '/objects/:name/hooks',
preHandler: fastify.auth([fastify.verifyAuthentication]),
schema: {
params: {
type: 'object',
properties: {
name: { type: 'string' }
}
},
body: {
type: 'object',
required: ['url'],
properties: {
url: { type: 'string', format: 'uri' }
}
},
response: {
201: {
type: 'object',
properties: {
id: { type: 'string', format: 'uuid' }
}
}
}
},
handler:
/**
* @type {FastifyHandler}
*/
async (req, res) => {
const {
params: { name },
body: { url }
} = req;

const hookId = await fastify.createHook({
name,
url
});

if (fastify.log.security) {
fastify.log.security({
success: true,
message: `Hook "${hookId}" sucessfully created for object "${name}"`,
category: 'database',
type: ['creation', 'allowed'],
method: req.method,
url: req.url,
requestId: req.id,
sourceAddress: req.ip,
host: req.hostname
});
}

res.header('Cache-Control', 'no-storage');
res.status(201).send({ id: hookId });
}
});

fastify.route({
method: 'DELETE',
url: '/objects/:name/hooks/:id',
preHandler: fastify.auth([fastify.verifyAuthentication]),
schema: {
params: {
type: 'object',
properties: {
name: { type: 'string' },
id: { type: 'string', format: 'uuid' }
}
},
response: {
204: {}
}
},
handler:
/**
* @type {FastifyHandler}
*/
async (req, res) => {
const {
params: { name, id }
} = req;

const hook = await fastify.getHook({ name, id });
if (!hook) {
throw fastify.httpErrors.notFound(
`Hook '${id}' not found for object '${name}'`
);
}

await fastify.deleteHook({ name, id });

if (fastify.log.security) {
fastify.log.security({
success: true,
message: `Hook "${id}" sucessfully deleted for object "${name}"`,
category: 'database',
type: ['deletion', 'allowed'],
method: req.method,
url: req.url,
requestId: req.id,
sourceAddress: req.ip,
host: req.hostname
});
}

res.header('Cache-Control', 'no-storage');
res.status(204);
}
});
};
Loading

0 comments on commit dbc8543

Please sign in to comment.