How to use object as keys in an elegant hash-style way - javascript

I have the following code:
const { query1 } = require('query1')
const { query2 } = require('query2')
const { query3 } = require('query3')
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
query1,
query2,
query3
}
})
});
const permissions = shield(
{
Query: {
query1: user,
query2: user,
query3: admin
}
}
)
(much longer in the reality)
And I'm looking for a way to make it clearer, like:
const { query1 } = require('query1')
const { query2 } = require('query2')
const { query3 } = require('query3')
const declaration = {
query1: user,
query2: user,
query3: admin
}
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: someMagic(declaration)
})
});
const permissions = shield(
{
Query: declaration
}
)
But here declaration keys are the strings "query1", "query2" and "query3". Not the objects.
With a WeakMap we could have something like:
const declaration = new WeakMap();
declaration.set(query1, user);
declaration.set(query2, user);
declaration.set(query3, admin);
But I find it much less elegant. Is there another way ?

Hope this might help:
/*
queries['query1'] = require('query1')
queries['query2'] = require('query2')
queries['query3'] = require('query3')
*/
let queries = {
query1: { a: { $eq: "I am Query 1" }, permission: "user" }, //user can be String or object or whatever !
query2: { b: { $eq: "I am Query 2" }, permission: "user" },
query3: { c: { $eq: "I am Query 3" }, permission: "admin" },
};
let declaration = {};
let queryNames = Object.keys(queries);
for (let i in queryNames) {
let curQueryName = queryNames[i];
declaration[curQueryName] = queries[curQueryName]["permission"];
//delete queries[curQueryName]["permission"]
}
console.log(JSON.stringify(declaration, null, 2));

Instead of an object, you could have an array of objects, something like this:
const { query1 } = require('query1')
const { query2 } = require('query2')
const { query3 } = require('query3')
var declarations = [
{ query1, permissions: user },
{ query2, permissions: user },
{ query3, permissions: admin }
];
Then to extract fields and permissions objects:
var fields = {};
var queryPermissions = {};
for (let declaration of declarations) {
for (let key of Object.keys(declaration)) {
if (key !== 'permissions') {
fields[key] = declaration[key];
queryPermissions[key] = declaration.permissions;
}
}
}
For example:
const query1 = { query: 'sample query 1' };
const query2 = { query: 'sample query 2' };
const query3 = { query: 'sample query 3' };
const user = 'user';
const admin = 'admin';
var declarations = [
{ query1, permissions: user },
{ query2, permissions: user },
{ query3, permissions: admin }
];
var fields = {};
var queryPermissions = {};
for (let declaration of declarations) {
for (let key of Object.keys(declaration)) {
if (key !== 'permissions') {
fields[key] = declaration[key];
queryPermissions[key] = declaration.permissions;
}
}
}
console.log(fields);
console.log(queryPermissions);
Side Note:
Another advantage of doing it this way is that you can group queries by permissions, for instance:
var declarations = [
{ query1, query2, permissions: user },
{ query3, permissions: admin }
];

It's not possible to construct an object that uses a variable's name as the property name but something else than the variable's value as the value. You'll have to spell them out twice, once for the permissions once for the resolvers:
const queryPermissions: {
'query1': user,
'query2': user,
'query3': admin,
};
const queryResolvers: {
'query1': require('query1').query1,
'query2': require('query2').query2,
'query3': require('query3').query3,
};
The destructured variables from the imports don't really help with anything here. However, if your module structure is really like this, and you're still using Common.js modules, then you can actually derive the queryResolvers object from the property names of the queryPermissions object:
const queryResolvers = Object.fromEntries(Object.keys(queryPermissions).map(fieldName =>
[fieldName, require(fieldName)[fieldName]]
));

This should do the trick:
((function(t,d){
var fields = {}
for (const key in d) {
fields[key] = t[key]
}
return fields
})(this, declaration)
{a, b, c}
Is just shorthand for {a: a, b: b, c: c}
However, Global constants do not become properties of the window object, unlike var variables, so you might need to fiddle a bit with it.

Related

How to implement roles if user have more than one roles in Reactjs?

Previously I've created with Custom Hook and it works but only for 1 role .
How do I make it work for users who have more than 1 role?
example of user roles:
User 1 = ["Super Admin", "Marketing"]
User 2 = ["Marketing", "Customer Service"]
This code for Role Map:
enum ROLES {
SUPER = "Super Admin",
MARKETING = "Marketing",
CUSTOMER_SERVICE = "Customer Service"
};
export const rolesMaps = (role) => {
switch (role) {
case ROLES.SUPER_ADMIN: {
return {
adminUserFeature: "READ WRITE DELETE",
homePageFeature: "READ WRITE DELETE",
};
}
case ROLES.MARKETING : {
return {
adminUserFeature: "READ WRITE DELETE",
homePageFeature: "READ WRITE ",
};
}
case ROLES.CUSTOMER_SERVICE : {
return {
adminUserFeature: "READ",
homePageFeature: "READ",
};
}
default:
return {};
}
}
This code for Custom Hook
import { useMemo } from "react";
import { rolesMaps } from "./rolesMaps"; //Role Map
export const usePermissions = (role, feature) => {
return useMemo(() => {
const permissions = rolesMaps(role && role[0])[feature];
console.log("permisi", permissions);
return {
canRead: !!permissions?.includes("READ"),
canWrite: !!permissions?.includes("WRITE"),
canDelete: !!permissions?.includes("DELETE"),
};
}, [role, feature]);
}
This code for implement on component
const role = useGetRole()
const { canWrite } = usePermissions(role, "homePageFeature");
{canWrite && <Button variant="primary" onClick={handleAddNew}>Add New</Button>}
For your role map, you could switch to using a key value pair where the value is an array of strings. This way you can check for duplicates easier, Here is my implementation.
enum ROLES {
SUPER_ADMIN = "Super Admin",
MARKETING = "Marketing",
CUSTOMER_SERVICE = "Customer Service"
};
// you can now pass in multiple roles into ROLES
export const rolesMaps = (roles: ROLES[]) => {
const permissions: {[key: string]: string[]} = {};
const addPermission = (featureName: string, permissionsToAdd: string[]) => {
if (permissions[featureName] === undefined) {
permissions[featureName] = [];
}
for (const perm of permissionsToAdd) {
if (permissions[featureName].indexOf(perm) === -1) {
permissions[featureName].push(perm);
}
}
}
for (const role of roles) {
switch (role) {
case ROLES.MARKETING:
case ROLES.SUPER_ADMIN: {
// add permissions that both marketing and super_admin share
addPermission("adminUserFeature", ["READ", "WRITE", "DELETE"]);
addPermission("homePageFeature", ["READ", "WRITE"]);
if (role === ROLES.SUPER_ADMIN) {
// only admin role can delete in the homePageFeature
addPermission("homePageFeature", ["DELETE"]);
}
}
case ROLES.CUSTOMER_SERVICE : {
addPermission("adminUserFeature", ["READ"]);
addPermission("homePageFeature", ["READ"]);
}
}
}
// the return value is now a {[key: string]: string[]}, the value is an array of strings
// If you want the value to still be the same syntax as in your question
// You can use permissions[featureName].join(" ") to get a string of roles seperated by a space
// But the current usage of the return value should still work as arrays also have the .include() method
return permissions;
}
I have used liked this
export const useRole = ([allowedRoles]) => {
const { user } = useAuth()
const history = useHistory()
useEffect(() => {
return () => {
if (!allowedRoles.includes(user?.role)) {
history.push('/error')
}
}
}, [])
}

How do I define Azure Cosmos DB container composite indexes in javascript?

I'm trying to create a container and define the composite indexes that I want using Azure Cosmos DB, Azure functions, and node with typescript. I have a config file that defines my options which I read from. I then try to pass in the composite index json object into my createIfNotExists containers function, but no luck. Any help would be appreciated!
config.ts
export const config = {
endpoint: <MyUrl>,
key: <MyKey>,
databaseId: MyDBId,
myContainerId: <MyContainerId>,
myPartitionKey: { kind: "Hash", paths: ["/id"] },
myContainerOptions: {
"compositeIndexes":[
[
{
"path":"/myId",
},
{
"path":"/myOtherId",
}
]
]
}
};
my azure function code:
const myId= (req.query.myId || (req.body && req.body.myId));
const myOtherId = (req.query.myOtherId || (req.body && req.body.myOtherId));
const { endpoint, key, databaseId, myContainerId, myPartitionKey, myContainerOptions } = config;
const client = new CosmosClient({ endpoint, key});
const database = client.database(databaseId);
const container = database.container(myContainerId);
await create(client, databaseId, myContainerId, myPartitionKey, myContainerOptions);
DbContext.ts
export async function create(client, databaseId, containerId, partitionKey, options?) {
const { database } = await client.databases.createIfNotExists({ id: databaseId });
const { container } = await database.containers.createIfNotExists({ id: containerId, partitionKey}, options);
console.log(`Created Database: ${database.id}\n Created container:\n${container.id}`);
}
Composite indexes should be part of container definition and not part of options.
Can you try with the following:
const containerDefinition = {
id: containerId,
partitionKey,
indexingPolicy: {
compositeIndexes: [
[
{ "path":"/myId"},
{ "path":"/myOtherId"}
]
]
}
};
const { container } = await database.containers.createIfNotExists(containerDefinition, options);

Creating a obj name with data in it in vue.js

I just created a constructor function to create new Users for a JSON file.
The structure should be like:
{
"users": {
"userName1": {
"salary": [
"1234"
]
},
"userName2": {
"salary": [
"4321"
]
}
}
}
My code looks like this atm:
export const userDataControllerMixin = {
data() {
return {
userObj: {},
};
},
methods: {
NewUser(user, salary) {
this.user = user;
this.salary = salary;
user = {
salary,
};
},
// GETTING INPUT FROM USERS DIALOGBOX
getInput(inputName, inputSalary) {
const userName = document.querySelector(inputName).value;
const userSalary = document.querySelector(inputSalary).value;
const userData = new this.NewUser(userName, userSalary);
console.log(userData);
},
The structur i get is wrong, it looks like this:
NewUser {user: "asd ", salary: "123"}
When you use the word this, it means the current father, in your case NewUser
To get the variable the way you want, you need to do this:
NewUser(user, salary) {
this[user] = {
'salary':salary
};
},
In VueJS there is no need for querySelectors, since inputs are binded with v-model
Check out: https://v2.vuejs.org/v2/guide/forms.html
Because of that, we can reduce the app to one function, that reads the username and salary properties and adds them to the userObj.
I've made a working example here: https://codepen.io/bergur/pen/agZwQL?editors=1011
new Vue({
el: '#app',
data() {
return {
username: '',
salary: '',
userObj: {}
}
},
methods: {
newUser() {
this.userObj[this.username] = {
salary: [Number(this.salary)]
}
console.log(this.userObj)
}
}
})

ES6 dynamic destructuring

I need to apply different destructuring for the function response depending of global flag [single service for multiple apps]
// Destructuring template should be defined as value
let destructuringTemplate;
if (flag) {
destructuringTemplate = {data: {classA: user}};
} else {
destructuringTemplate = {data: {classB: user}};
}
// This technique would not work, this is just my idea representation.
this.getUser(({destructuringTemplate: user) => { this.localUser = user });
At this moment it works this way:
let destructuringTemplate;
if (flag) {
destructuringTemplate = ({data: {classA: user}}) => user;
} else {
destructuringTemplate = ({data: {classB: user}}) => user;
}
this.getUser(response => { this.localUser = destructuringTemplate(response)};
it is kinda ugly, some suggestion how it should be done?
You could use a computed property name with a conditional (ternary) operator ?:.
var flag = true,
object = { data: { classA: 'foo', classB: 'bar' } },
{ data: { [flag ? 'classA' : 'classB']: user } } = object,
{ data: { [!flag ? 'classA' : 'classB']: user1 } } = object;
console.log(user);
console.log(user1);
You don't need to use destructuring, use simple dot/bracket notation:
const userClass = flag ? 'classA' : 'classB'
this.getUser(response => { this.localUser = response.data[userClass] })
If you want to reuse this logic, just create simple function, e.g.:
const extractUser = response => response.data[userClass]

ES6 Classes for Data Models

I'm trying to use ES6 Classes to construct data models (from a MySQL database) in an API that I'm building. I prefer not using an ORM/ODM library, as this will be a very basic, simple API. But, I'm struggling to get my head around how to define these models.
My data entities are (these are just some simplified examples):
CUSTOMER
Data Model
id
name
groupId
status (enum of: active, suspended, closed)
Private Methods
_getState(status) {
var state = (status == 'active' ? 'good' : 'bad');
return state;
}
Requests
I want to be able to do:
findById: Providing a single customer.id, return the data for that specific customer, i.e. SELECT * FROM customers WHERE id = ?
findByGroupId: Providing a group.id, return the data for all the customers (in an array of objects), belonging to that group, i.e. SELECT * FROM customers WHERE groupId = ?
Response Payloads
For each customer object, I want to return JSON like this:
findById(1);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
}]
findByGroupId(2);:
[{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
GROUP
Data Model
id
title
Requests
I want to be able to do:
findById: Providing a single group.id, return the data for that specific group, i.e. SELECT * FROM groups WHERE id = ?
Response Payloads
For each group object, I want to return JSON like this:
findById(2);:
{
"id" : 2,
"title" : "This is Group 2",
"customers" : [{
"id" : 1,
"name" : "John Doe",
"groupId" : 2,
"status" : "active",
"state" : "good"
},
{
"id" : 4,
"name" : "Pete Smith",
"groupId" : 2,
"status" : "suspended",
"state" : "bad"
}]
}
Requirements:
Must use ES6 Classes
Each model in its own file (e.g. customer.js) to be exported
Questions:
My main questions are:
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
UPDATE!!
This is what I came up with - (would be awesome if someone could review, and comment):
CUSTOMER Model
'use strict';
class Cust {
constructor (custData) {
this.id = custData.id;
this.name = custData.name;
this.groupId = custData.groupId;
this.status = custData.status;
this.state = this._getState(custData.status);
}
_getState(status) {
let state = (status == 'active' ? 'good' : 'bad');
return state;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let custData = `do the MySQL query here`;
let cust = new Cust (custData);
let Group = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'group');
Group.findById(cust.groupId).then(
(group) => {
cust.group = group;
resolve (cust)
},
(err) => {
resolve (cust);
}
);
});
});
GROUP Model
'use strict';
class Group {
constructor (groupData) {
this.id = groupData.id;
this.title = groupData.title;
}
}
exports.findById = ((id) => {
return new Promise ((resolve, reject) => {
let groupData = `do the MySQL query here`;
if (id != 2){
reject('group - no go');
};
let group = new Group (groupData);
resolve (group);
});
});
CUSTOMER Controller (where the Customer model is instantiated)
'use strict';
var Cust = require(appDir + process.env.PATH_API + process.env.PATH_MODELS + 'cust');
class CustController {
constructor () {
}
getCust (req, res) {
Cust.findById(req.params.id).then(
(cust) => {
res(cust);
},
(err) => {
res(err);
}
)
}
}
module.exports = CustController;
This seems to be working well, and I've been able to use Class, Promise and let to make it more ES6 friendly.
So, I'd like to get some input on my approach. Also, am I using the export and required features correctly in this context?
Here is another approach,
Where would I define the data structure, including fields that require data transformation, using the private methods (e.g. _getState())
You should define those fields, relationship in your model class extending the top model. Example:
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
Should the findById, findByGroupId, etc by defined within the scope of the class? Or, should these by separate methods (in the same file as the class), that would instantiate the object?
Instead of having many functions use findByAttribute(attr) in Model Example:
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
How should I deal with the case where one object is a child of the other, e.g. returning the Customer objects that belongs to a Group object as an array of objects in the Group's findById?
In case of relationships, you should have methods like findRelations, getRelatedRecords.
var customer1 = new Customer({ id: 1, groupId: 3});
customer1.getRelatedRecords('Group');
class Model {
...
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
...
}
Where should the SQL queries that will connect to the DB be defined? In the getById, getByGroupId, etc?
This one is tricky, but since you want your solution to be simple put the queries inside your find methods. Ideal scenario will be to have their own QueryBuilder Class.
Check the following full code the solution is not fully functional but you get the idea. I've also added engine variable in the model which you can use to enhance fetching mechanism. All other design ideas are upto your imagination :)
FULL CODE:
var config = {
engine: 'db' // Ex: rest, db
};
class Model {
constructor(values) {
this.values = values;
this.engine = config.engine;
}
toObj() {
var data = {};
for (var key in this.values) {
if (this.values[key] instanceof Model) {
data[key] = this.values[key].toObj();
} else if (this.values[key] instanceof Array) {
data[key] = this.values[key].map(x => x.toObj());
} else {
data[key] = this.values[key];
}
}
return data;
}
static findByAttribute(attr) {
return new Promise((resolve, reject) => {
var query = this._convertObjectToQueriesArray(attr);
query = query.join(" and ");
let records = `SELECT * from ${this.getResourceName()} where ${query}`;
var result = this.run(records);
// Note: Only support 'equals' and 'and' operator
if (!result) {
reject('Could not found records');
} else {
var data = [];
result.forEach(function(record) {
data.push(new this(record));
});
resolve(data);
}
});
}
getRelatedRecords(reln) {
var targetRelationship = this.relationships()[reln];
if (!targetRelationship) {
throw new Error("No relationship found.");
}
var primaryKey = this._getPrimaryKey();
var relatedObject = eval(reln);
var attr = {};
if (targetRelationship.type == "hasOne") {
console.log(this.values);
attr[relatedObject.prototype._getPrimaryKey()] = this.values[targetRelationship.foreignKey];
} else if (targetRelationship.type == "hasMany") {
attr[targetRelationship.foreignKey] = this.values[this._getPrimaryKey()];
}
relatedObject.findByAttribute(attr).then(function(records) {
// this.values[reln] = records;
});
}
/**
* Test function to show what queries are being ran.
*/
static run(query) {
console.log(query);
return [];
}
_getPrimaryKey() {
for (var key in this.attributes()) {
if (this.attributes()[key].primary) {
return key;
}
}
}
/**
* Convert Object of key value to sql filters
*
* #param {Object} Ex: {id:1, name: "John"}
* #return {Array of String} ['id=1', 'name=John']
*/
static _convertObjectToQueriesArray(attrs) {
var queryArray = [];
for (var key in attrs) {
queryArray.push(key + " = " + attrs[key]);
}
return queryArray;
}
/**
* Returns table name or resource name.
*
* #return {String}
*/
static getResourceName() {
if (this.resourceName) return this.resourceName();
if (this.constructor.name == "Model") {
throw new Error("Model is not initialized");
}
return this.constructor.name.toLowerCase();
}
}
class Customer extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
name: {
type: 'string'
},
groupId: {
type: 'integer'
},
status: {
type: 'string'
},
state: {
type: 'string'
}
};
}
relationships() {
return {
'Group': {
type: 'hasOne',
foreignKey: 'groupId'
}
};
}
}
class Group extends Model {
attributes() {
return {
id: {
type: 'integer',
primary: true
},
title: {
type: 'string'
}
};
}
relationships() {
return {
'Customer': {
type: 'hasMany',
foreignKey: 'groupId'
}
};
}
}
var cust = new Customer({
id: 1,
groupId: 3
});
cust.getRelatedRecords('Group');
var group = new Group({
id: 3,
title: "Awesome Group"
});
group.getRelatedRecords('Customer');
var groupData = new Group({
"id": 2,
"title": "This is Group 2",
"customers": [new Customer({
"id": 1,
"name": "John Doe",
"groupId": 2,
"status": "active",
"state": "good"
}),
new Customer({
"id": 4,
"name": "Pete Smith",
"groupId": 2,
"status": "suspended",
"state": "bad"
})
]
});
console.log(groupData.toObj());

Categories