@universis/user-storage
Version:
Universis Api Server Extension for managing user remote storage
187 lines (173 loc) • 6.86 kB
JavaScript
import {AccessDeniedError} from '@themost/common';
import * as redis from 'redis';
import {UserStorageService, UserStorageAccessConfiguration} from './service';
import {promisify} from 'es6-promisify';
const REDIS_JSON_COMMANDS = ["json.del", "json.get", "json.mget", "json.set", "json.type",
"json.numincrby", "json.nummultby", "json.strappend", "json.strlen", "json.arrappend", "json.arrindex",
"json.arrinsert", "json.arrlen", "json.arrpop", "json.arrtrim", "json.objkeys", "json.objlen",
"json.debug", "json.forget", "json.resp"];
function createJsonClient(options) {
// add json commands
REDIS_JSON_COMMANDS.forEach (command => {
// noinspection JSUnresolvedFunction
redis.addCommand(command);
});
// and finally return client
return redis.createClient(options);
}
class RedisUserStorageService extends UserStorageService {
/**
* Formats a path like key string to a redis command compatible key e.g. user1/application1/lastAction to .user1.application1.lastAction
* @param key
* @returns {string|*}
*/
static escapeKey(key) {
let res = key.replace(/\//ig,'.');
if (/^\./.test(res)) {
return res;
}
return '.' + res;
}
/**
* @param {IApplication} app
*/
constructor(app) {
super(app);
}
/**
* @name RedisClient#json_set
* @param {*} ...args
*/
/**
* @name RedisClient#json_get
* @param {*} ...args
*/
/**
*
* @param {DataContext} context
* @param {string} key
* @param {*} value
* @param {number=} expiration
*/
async setItem(context, key, value, expiration) {
// validate user
const hasAccess = await context.getConfiguration().getStrategy(UserStorageAccessConfiguration).verify(context, `me/${key}`, 'write');
if (hasAccess == null) {
throw new AccessDeniedError('Write access to the specified user storage key is denied');
}
// create redis client
const client = createJsonClient(this.options);
const json_set = promisify( client.json_set).bind(client);
// noinspection JSUnresolvedVariable
const json_type = promisify( client.json_type).bind(client);
const quit = promisify( client.quit).bind(client);
// validate root object
//split key e.g. .key1.key2.key3 to an array of [ '', 'key1', 'key2', 'key3' ]
const keys = UserStorageService.escapeKey(key).split('.');
// enumerate existence of keys from [1] to [length - 1] in order to avoid redis errors
for (let i = 0; i < keys.length-1; i++) {
let key1 = keys.slice(0, i + 1).join('.');
// ensure root key
if (i === 0) {
key1 = '.';
}
// validate key e.g. .key1 and .key1.key2
const value1 = await json_type(`${context.user.name}`, key1);
// if key does not exist
if (value1 == null) {
// set value
await json_set(`${context.user.name}`, key1, JSON.stringify({ }));
}
}
if (typeof expiration === "number" && expiration > 0) {
// call json_set() with expiration
await json_set(`${context.user.name}`, UserStorageService.escapeKey(key), 'EX', Math.ceil(expiration), JSON.stringify(value));
// close client
await quit();
// and return
return value;
}
// call json_set() with expiration
await json_set(`${context.user.name}`, UserStorageService.escapeKey(key),JSON.stringify(value));
// close client
await quit();
// and return
return value;
}
/**
* @param {DataContext} context
* @param {string} key
*/
async getItem(context, key) {
// validate user
const hasAccess = await context.getConfiguration().getStrategy(UserStorageAccessConfiguration).verify(context, `me/${key}`, 'read');
if (hasAccess == null) {
throw new AccessDeniedError('Read access to the specified user storage key is denied');
}
// create redis client
const client = createJsonClient(this.options);
// noinspection JSUnresolvedVariable
const json_type = promisify( client.json_type).bind(client);
const json_get = promisify( client.json_get).bind(client);
// noinspection JSUnresolvedVariable
const quit = promisify( client.quit).bind(client);
// call json_get()
const type = await json_type(`${context.user.name}`, UserStorageService.escapeKey(key));
if (type == null) {
return null;
}
// get value
const res = await json_get(`${context.user.name}`, 'NOESCAPE', UserStorageService.escapeKey(key));
// quit
await quit();
// if value is not null
if (res != null) {
// parse string value and return
return JSON.parse(res);
}
return res;
}
/**
* @param {DataContext} context
* @param {string} key
* @returns Promise<*>
*/
async removeItem(context, key) {
// validate user access
const hasAccess = await context.getConfiguration().getStrategy(UserStorageAccessConfiguration).verify(context, `me/${key}`, 'write');
if (hasAccess == null) {
throw new AccessDeniedError('Write access to the specified user storage key is denied');
}
// create redis client
const client = createJsonClient(this.options);
// noinspection JSUnresolvedVariable
const json_del = promisify( client.json_del).bind(client);
const quit = promisify( client.quit).bind(client);
// call json_del()
const res = await json_del(`${context.user.name}`, UserStorageService.escapeKey(key));
// close client
await quit();
// and return
return (res === 1);
}
/**
* @param {DataContext} context
* @returns Promise<*>
*/
async clear(context) {
// validate user access
const hasAccess = context.getConfiguration().getStrategy(UserStorageAccessConfiguration).verify(context, `me`, 'write');
if (hasAccess == null) {
throw new AccessDeniedError('Write access to the specified user storage key is denied');
}
// create redis client
const client = createJsonClient(this.options);
const del = promisify( client.del).bind(client);
const quit = promisify( client.quit).bind(client);
// call json_del()
const res = await del(`${context.user.name}`);
await quit();
return (res === 1);
}
}
module.exports.RedisUserStorageService = RedisUserStorageService;