Pushing array into object that is contained in array - javascript

UPDATED: Thanks to everyone for the help
I'm getting 2 collections from Firestore: Roles and Apps. Apps is a subcollection of Roles so it is contained inside of Roles collection, therefore, to get the apps I need to get the Roles document where they are contained first. I have no problem doing this but I need to save them as they are stored in firebase to group them later, I can't figure out how. I want some data like this:
[
{
roleDescription: 'System Administrator',
active: true,
roleId: '1'
apps: [
{
appId: '2',
appDescription: 'Manage Roles',
groupId: '1',
route: 'roles',
appName: 'Roles',
active: true
},
{
appId: '1',
appDescription: 'Users',
groupId: '1',
route: 'users',
appName: 'Users',
active: true
},
{
...
}
]
},
{
active: true,
roleId: '2',
roleDescription: 'Employee',
apps: [
{
appId: '5',
appDescription: 'Upload Data',
groupId: '1',
route: 'roles',
appName: 'Roles',
active: true
},
{
appId: '1',
appDescription: 'Users',
groupId: '1',
route: 'users',
appName: 'Users',
active: true
},
{
...
}
]
}
]
Currently I have this code where I can get all the roles in snapshot and map them to get every role individually and then with snapshot2 get the apps that are contained inside that role also individually to assign every app in snapshot2 to to the object or array of roles contained also in an array.
Here is my code:
ref.get()
.then(function(snapshot) {
return Promise.all(snapshot.docs.map(doc => {
var AppRole = {};
AppRole.role = doc.data();
roles.push(AppRole);
return doc.ref.collection('apps').get()
.then((snapshot2) => {
return snapshot2.docs.map(app => {
roles[count].apps = app.data(); // Here I need to push app.data() to roles[count].apps and problem solver but I don't know how to do it (roles[count].apps is an object and I know it doesnt have the push method but I tried with a counter also like roles[count].apps[i] = app.data(); i++; but no success )
})
})
console.log(count);
count++;
}))
.then(() => {
console.log(roles);
res.render("pages/roles", { name: req.session.name, idiom: req.session.idiom, roles: roles, apps: apps, pageInfo: req.session.lang.roles});
})

You should be able to do this without any counters. I've commented the main changes in the code.
Basically, just keep a reference to the role after you create it. Then you can assign a list of apps to it all at once by using map().
const roles = []; // You can add and remove elements to a const array
const db = admin.firestore();
const ref = db.collection('roles');
ref.get()
.then(function(roleQuery) {
return Promise.all(roleQuery.docs.map(roleDoc => {
// ** Create a new role and keep a reference to it **
const role = roleDoc.data()
roles.push(role);
return roleDoc.ref.collection('apps').get()
.then((appQuery) => {
// ** Easily create a new array by calling
// ** map() on the app documents
role.apps = appQuery.docs.map(appDoc => {
return appDoc.data();
})
// ** alternate syntax:
// ** appQuery.docs.map(appDoc => appDoc.data())
})
}))
.then(() => {
console.log(roles);
res.render("pages/roles", { name: req.session.name, idiom: req.session.idiom, roles: roles, apps: apps, pageInfo: req.session.lang.roles});
})

Don't really understand the full scope of your code, but why not just create an object that assigns apps to roles, and store the end result in the roles array?
You shouldn't need the j counter in this instance. I would imagine it looks something like this:
let roles = [];
const count = 0;
const db = admin.firestore();
const ref = db.collection('roles');
ref.get()
.then(function(querySnapshot) {
return Promise.all(querySnapshot.docs.map(doc => {
var AppRole = {}; //Declare AppRole object
AppRole.role = doc.data(); //Based on your question, this should return one role
roles.push(AppRole); //Adds object to array (You can use 'roles[count] = AppRole' if you feel more comfortable)
return doc.ref.collection('apps').get()
.then((snapshot2) => {
return snapshot2.docs.map(app => {
roles[count].apps = app.data(); //Based on your question, this should return an array of apps. That array should be stored within that particular object.
})
})
console.log(count);
count++;
}))
.then(() => {
console.log(roles);
res.render("pages/roles", { name: req.session.name, idiom: req.session.idiom, roles: roles, apps: apps, pageInfo: req.session.lang.roles});
})

Related

How to push multiple objects into a React state variable

I have a state variable that stores an object which contains the array invitees as a value:
const [values, setValues] = useState({
date: '',
location: '',
invitees: []
})
I have an array containing multiple objects:
users = [
{ _id: 'id1', name: 'name1', email: 'email1' },
{ _id: 'id2', name: 'name2', email: 'email2' },
{ _id: 'id3', name: 'name3', email: 'email3' }
]
I'd like to be able to push each of these objects into the values.invitees array.
I can do this for individual objects using a function like this:
const addInvitee = (user) => {
setValues({ ...values, invitees: [...values.invitees, user] })
}
But my problem is when I try to add multiple objects at once.
I have tried mapping through them like this:
users.map((user) => {
addInvitee(user)
})
And also using a for loop:
for (let i = 0; i < users; i++) {
addInvitee(users[i])
}
However both these attempts result in only the last object element in the array being added.
Is anyone able to help me understand why this is happening and what I can do to fix it. Many thanks.
Problem
That is because values reffered inside the addInvitee is the same initial values (empty array) not the updated values.
const addInvitee = (user) => {
setValues({ ...values, invitees: [...values.invitees, user] })
}
Solution
To avoid the stale value issue you should use the callback function of setter of the useState hook.
Try like below:
const addInvitee = (user) => {
setValues((prevValues) => ({
...prevValues,
invitees: [...prevValues.invitees, user]
}));
};
In this case, prevValues is always guaranteed to be the updated values.

Mui-DataTables customBodyRenderLite, render multiple links in one cell

I've also created an issue in their github for further help
I am creating an app with a lot of relationships and with mui-datatables want to create links to different profiles within the cells. Sometimes a single cell may need to contain more than one link otherwise I would use the onCellClick function.
An example of this use case might be Pet owners and their pets (this isn't actually my use-case, just an example).
Say I create a table of Owners and then in one cell is a list of the pet names
Owners
Pet Names
Paige
Sally, Sage, Ember
I've gotten it to where I can push all the names of the animals related to a specific owner to a new array and then join that array to create a string that shows up properly with all the pets listed. When I click on the row I get taken to the owner profile with the onRowClick function which is exactly how I want it
Code Example
const [data, setData] = useState([]);
const [petColumns, setPetColumns] = useState([]);
const [columnData, setColumnData] = useState([]);
const [options, setOptions] = useState({
filter: true,
filterType: 'dropdown',
responsive: 'standard',
rowsPerPage: 10,
resizableColumns: true,
enableNestedDataAccess: '.',
selectableRows: 'none',
onRowClick: (rowData) => {
handleClick(rowData);
}
})
setPetColumns = [
{
name: "id",
label: "Id",
options: {
display: 'excluded',
}
},
{
name: "name",
label: "Owner Name",
},
{
name: "pet",
label: "Pets",
},
]
useEffect(() => {
API.getOwnerData(ownerId)
.then(res => setData(res.data))
.catch(err => console.log(err))
}, [ownerId])
useEffect(() => {
const ownerData = data.map((owner) => {
const petList = []
owner.Pets.forEach((pet) => {
petList.push({
name: pet.name,
})
})
const petObject = {
id: owner.id,
name: `${owner.User.firstName} ${owner.User.lastName}`,
pet: petList.map(p => p.name).join(', '),
}
return petObject
})
setColumnData(ownerData)
}, [data])
const handleClick = (rowData) => {
navigate(`${rowData[0]}`)
}
<MUIDataTable
className={styles.innerTable}
data={columnData}
columns={petColumns}
options={options}
>
</MUIDataTable>
Where I'm running into trouble is that I want to turn each of the pet names that are being listed into a link that will navigate to the pets respective profile when clicked on. I will show some of my attempts to do this and explain what the results were.
ATTEMPT 1
Here I added pet.id to the petList object. This id is used with react-router-dom and Link to travel to the proper profile page for the pet. I then set up a customBodyRender (I tried both customBodyRender and customBodyRenderLite) which is supposed to map through the pet list and create a link with the pet name for each entry and then join together the new link divs.
I'm actually fairly stumped on this because I keep getting "Uncaught TypeError: value.map is not a function". Which makes me think that the value of property is not being recognized as an array. However, I found several StackOverFlow posts where value is treated properly as an array and can be joined together such as here.
I also recognize though that the array in that stack overflow is flat which mine is not but I even added the "enableNestedDataAccess: '.'," to that specific set of options (even though it's already in the general options for the table) just to make sure. I tried some variations of metadata as well but couldn't get that to stick either, I honestly think I'm probably incorporating it wrong.
CODE EXAMPLE - I didn't include the code that remained unchanged
setPetColumns = [
{
name: "id",
label: "Id",
options: {
display: 'excluded',
}
},
{
name: "name",
label: "Owner Name",
},
{
name: "pet",
label: "Pets",
options: {
customBodyRenderLite: (value) => { ****ATTEMPTED customBodyRender TO RENDER THE LINK AND THEN JOIN ****
return (
<>
{value.map((e) => {
<Link to={`/pets/id=${e.id}`} >{e.name}</Link>
}).join(', ')}
</>
)
}
}
},
]
useEffect(() => {
const ownerData = data.map((owner) => {
const petList = []
owner.Pets.forEach((pet) => {
petList.push({
name: pet.name,
id: pet.id ****ID IS ADDED FOR NAVIGATION PURPOSES****
})
})
const petObject = {
id: owner.id,
name: `${owner.User.firstName} ${owner.User.lastName}`,
pet: petList
}
return petObject
})
setColumnData(ownerData)
}, [data])
When I console log columnData I get exactly what I'm expecting
0:
id: "30c9"
name: "Paige"
property: Array(3)
0:
id: "df43"
name: "Sally"
1:
id: "dea4"
name: "Sage"
2:
id: "ger7"
name: "Ember"
ATTEMPT 2
My attempt here was kind of a hail mary, I'd been working trying to get the custom render to work for a long while so I thought I'd go at it from a different direction. I trying to include Link within the petObject that initially worked in providing the plain joined names.
My result of this attempt was that my data table rendered but I got [ object Object ] within the pet Columns if I included return in the .map function or an empty string if there was no return. I honestly didn't really expect this to work but was at a loss of what else to try. When I consoled the columnData it was either [ object Object ] or ""
setPetColumns = [
{
name: "id",
label: "Id",
options: {
display: 'excluded',
}
},
{
name: "name",
label: "Owner Name",
},
{
name: "pet",
label: "Pets",
},
]
useEffect(() => {
const ownerData = data.map((owner) => {
const petList = []
owner.Pets.forEach((pet) => {
petList.push({
name: pet.name,
id: pet.id ****ID IS ADDED FOR NAVIGATION PURPOSES****
})
})
const petObject = {
id: owner.id,
name: `${owner.User.firstName} ${owner.User.lastName}`,
*****TRIED TO INCLUDE LINK WITHIN OBJECT****
pet: petList.map((p) => {
return <Link to=`/pet/id=${p.id} >p.name </Link> ****If return isn't included a blank string is returned****
}).join(', '),
}
return petObject
})
setColumnData(ownerData)
}, [data])
Tech
Version
Material-UI
4.12.3
MUI-datatables
4.1.2
React
17.0.2
browser
Google Chrome

Why the state hook in react changes all elements of the object?

I have a list of members and each member has the same set of applications. I created 2 classes to associate each member and application and ended up with a general list of members and applications, which I wrote in the initial state.
Then, using the function, I get the ID of the member that was clicked and I want to change the state of a particular application for it from false to true. But I get a change in the state of the application from false to work for ALL members. Why is this happening and where could there be an error ???
list members
let teamMembers = [
{ 'idMember': 0, 'name': 'John Littel', 'email': 'Delores_Barrows5#hotmail.com' },
{ 'idMember': 1, 'name': 'Tom Hamill', 'email': 'Luigi0#gmail.com' },
{ 'idMember': 2, 'name': 'Ann Quitzonl', 'email': 'AnnQ#hotmail.com' },
{ 'idMember': 3, 'name': 'Frances Schuster', 'email': 'Frances.Schuster#yahoo.com' },
{ 'idMember': 4, 'name': 'Morrison Mohr', 'email': 'Rafael.Hilll64#yahoo.com' }
];
list apps:
let appsFromBase = ['App 0', 'App 1', 'App 2', 'App 3', 'App 4'];
class Application {
constructor(idApp, isSelected, appName) {
this.idApp = idApp;
this.isSelected = isSelected;
this.appName = appName;
}
toggleSelected() { *this method changed apps from false => true*
this.isSelected = !this.isSelected
}
};
const apps = appsFromBase.map((app, i) => new Application(i, false, app));
const membersObject = teamMembers.map((member, i) => new Member(i, member.name, member.email, apps));
State:
const [members, setMembers] = useState(membersObject);
Handler method:
const handleChoosenApp = (app, member_i) => {
let newMembers = { ...members };
let _id = app.idApp;
if (_id !== 'btn-all-app') {
let myUpdatingMember = newMembers[member_i].apps[_id].toggleSelected()
setMembers({ ...newMembers });
}
}
member_i, _id - This is the member's ID and his application, which was clicked from the interface and he needs to change the value from true to false
in the console myUpdatingMember I get the member and application I need, but why does the state change for everyone ???
The selected state is a property of the Application, not the Member.
You create one Application array and pass it to every Member constructor.
Every Member object you create shares the same Application array.

How to seed roles and capabilities in MongoDB

I am new to working on a MongoDB and Docker, I am working on an application and couldn't find a more subtle way to seed my database using an npm run command. First I created a file called seed.js and then associated it to npm run seed command on the package.json file.
On the seed.js file I import Mongoose and the models but two things I will need to do is:
Create roles, if they don’t exist yet
Create capabilities, if they don’t exist yet and associate it to the
roles
The Roles that i want to create are:
admin (description: Administrator)
viewer (description: Viewer)
Capabilities
I need to check each endpoint of the Users service that should require authentication and create an adequate capability. Example: updateUser updates the user data. This could be done by the own user (so there must be an updateUserOwn capability) and by an administrator (that will have an updateUsers capability). I will have to analyse each endpoint and judge what is adequate but I cannot still find a way around getting the initial role and capabilities to the database.
UPDATE:
On the seeding itself, the updated solution works, but it requires lot of code and repetition that could probably be fixed by loops. I’d like to start creating the roles first which means creating an array with objects, with the data from the roles to be created. Each role has the fields role and description
const userRole = [{
role: admin
description: Administrator
},
{
role: viewer
description: Viewer
}]
The idea is that if the role exist it doesn't need to update but I don't know how do I loop through the array and create a role only if it doesn’t exist. Something like using updateOne, with the upsert: true option, but with the data on $setOnInsert as this will add the data only if a document is inserted.
I only need create and not update because in the future I’ll edit roles directly through the API. So, if a change was made on the admin role, for example, the seed will not overwrite it
During the loop, I'll need to create an associative array called rolesIds that will store the ObjectId of the created roles. It should result in something like this:
[
"admin": "iaufh984whrfj203jref",
"viewer": "r9i23jfeow9iefd0ew0",
]
Also each capability must have an array of roles it must be associated to. Example:
{
capability: "updateUsers",
description: "Update the data of all users",
roles: ["admin"]
}
How do I loop through the array on each element, prepare it to be inserted using the array with object IDs. Instead of roles: ["admin"]? something like roles: ["iaufh984whrfj203jref"], otherwise there’ll be a cast error. Remember each capability may be associated to more than one role, so I'll probably need to loop through them but I cannot find a way to create that logic.
Users Model
const userSchema = new mongoose.Schema(
{
.......
role: {
ref: "roles",
type: mongoose.Schema.Types.ObjectId,
},
);
module.exports = mongoose.model("User", userSchema);
Role Model:
const roles = new mongoose.Schema({
role: {
type: String,
required: true,
},
capabilities: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "capabilities",
},
],
});
module.exports = mongoose.model("roles", roles);
Capabilities Model:
const capabilities = new mongoose.Schema({
capability: {
type: String,
required: true,
},
name: {
type: String,
},
});
module.exports = mongoose.model("capabilities", capabilities);
UPDATED: seed file:
const seedDB = async () => {
if (!process.env.DB_URI) {
throw new Error("Error connecting to MongoDB: DB_URI is not defined.");
}
try {
await mongoose.connect(process.env.DB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
console.log("Connected to MongoDB");
const tasks = [
Capability.findOneAndUpdate(
{ name: "updateUserOwn" },
{ capability: "updateUser" },
{ upsert: true }
).exec(),
Capability.findOneAndUpdate(
{ name: "updateUsers" },
{ capability: "updateUser" },
{ upsert: true }
).exec(),
// Seed more...
];
const [updateUserOwn, updateUsers] = await Promise.all(tasks);
Role.bulkWrite([
{
updateOne: {
filter: { role: "Admin" },
update: { capabilities: [updateUsers] },
upsert: true,
},
},
{
updateOne: {
filter: { role: "Viewer" },
update: { capabilities: [updateUserOwn] },
upsert: true,
},
},
]);
console.log("seeded data", tasks);
} catch (error) {
console.log(`Error connecting to MongoDB: ${error}`);
}
};
seedDB();
You are on the right path overall.
Because capabilities are used as a reference you'd have to fetch or create them (get a ref) before assigning them to a role.
This could be your seed logic:
const tasks = [
Capability.findOneAndUpdate(
{ name: 'updateUserOwn' }, // matches or creates this capability
{ capability: 'updateUser' }, // adds this to the object
{ upsert: true, new: true } // `new` guarantees an object is always returned
}).exec(),
Capability.findOneAndUpdate(
{ name: 'updateUsers' },
{ capability: 'updateUser' },
{ upsert: true, new: true }
}).exec(),
// Seed more...
];
const [
updateUserOwn,
updateUsers,
] = await Promise.all(tasks);
// We can use bulk write for the second transaction so it runs in one go
await Role.bulkWrite([
{
updateOne: {
filter: { role: 'Admin' },
update: { capabilities: [updateUsers] },
upsert: true,
}
},
{
updateOne: {
filter: { role: 'Viewer' },
update: { capabilities: [updateUserOwn] },
upsert: true,
}
}
]);
We seed capabilities one by one using findOneAndUpdate so we can get a reference to each capability we intend to use on the roles
Then we use bulkWrite to seed the roles
I might have swapped the capabilities and their names but I hope you get the general idea
The seed would have been simpler if there weren't references involved - you could just use bulkWrite everything in one go, but in order to create object with inner references or add references to such object you first need to have the actual reference
You can create static mapping and loop through which would reduce the code a bit, and make things easier. This would also allow you to skip seeding items that already exist
Since capabilities are reused through roles I want to create them first, but it's no problem to alter the logic to first create roles and then capabilities, though it might not be as straight forward
Also each capability must have an array of roles it must be associated to.
This is called a "many to many" relationship (as roles also have an array of references to capabilities) which would only complicate logic. Are you sure you really need it - mongoose/monogo won't manage it automatically for you:
when you add a capability to a role you'd also need to sync and add the role inside capability.roles - manually
and the reverse - adding a role inside capability.roles you'd need to sync this and also manually add the capability to role.capabilities
the same thing for deleting capabilities or roles - manual cleanup
it can fail and would need to recover - e.g. a capability is added to role.capabilities but for some reason execution stopped and the role was not added to capability.roles - so the whole handling might need to be wrapped in a transaction
there are ways to cross reference roles and capabilities without have to have a "many to many" relationship
Here's a simple approach using save middleware to sync many to many relationships for create/update
Role.js
const mongoose = require('mongoose');
const roles = new mongoose.Schema({
role: {
type: String,
required: true,
},
description: String,
capabilities: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'capabilities',
},
],
});
roles.pre('save', async function save() {
// Doesn't need to run if there are no capabilities
if (!this.capabilities || this.capabilities.length === 0) return;
const Capability = mongoose.model('capabilities');
await Capability.updateMany(
{ _id: {$in: this.capabilities} },
// Adds only if it's missing
{ $addToSet: { roles: this._id }},
);
});
// Todo: similar logic to remove from capabilities if role is deleted
module.exports = mongoose.model("roles", roles);
Capability.js
const mongoose = require('mongoose');
const capabilities = new mongoose.Schema({
capability: {
type: String,
required: true,
},
description: {
type: String,
},
roles: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'roles',
}
]
});
capabilities.pre('save', async function save() {
if (!this.roles || this.roles.length === 0) return;
const Role = mongoose.model('roles');
await Role.updateMany(
{_id: {$in: this.roles}},
{$addToSet: {capabilities: this._id}},
);
})
// Todo: similar logic to remove from roles if capability is deleted
module.exports = mongoose.model("capabilities", capabilities);
Here's an update seed routine:
Seed.js
const mongoose = require('mongoose');
const Capability = require('./models/Capability');
const Role = require('./models/Role');
const CAPABILITIES = {
UPDATE_USERS: {
capability: 'updateUsers',
description: 'Update the data of all users',
},
VIEW_USERS: {
capability: 'viewUsers',
description: 'View public data of users',
},
UPDATE_OWN_RECORD: {
capability: 'updateUserOwn',
description: 'Update user own data',
}
}
const ROLES_TO_SEED = [
{
role: 'admin',
description: 'Administrator',
capabilities: [CAPABILITIES.UPDATE_USERS, CAPABILITIES.VIEW_USERS],
},
{
role: 'viewer',
description: 'Viewer',
capabilities: [CAPABILITIES.VIEW_USERS, CAPABILITIES.UPDATE_OWN_RECORD],
}
]
const seedDB = async () => {
await connectToDb();
await seedRoles();
};
const connectToDb = async () => {
if (!process.env.DB_URI) throw new Error('DB_URI is not defined.');
console.info('Connecting to database...');
await mongoose.connect(process.env.DB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false,
});
console.info('Connected \n');
}
const seedRoles = async () => {
console.log('Seeding Roles...');
// runs sequentially to skip creating duplicate capabilities
for (const role of ROLES_TO_SEED) {
await findOrCreateRole(role);
}
console.log('Complete \n');
}
const findOrCreateRole = async ({capabilities, role, ...defaults}) => {
console.info('Looking for role: ', role);
const fromDb = await Role.findOne({role}).exec();
if (fromDb) {
console.info('Role already exists skipping... \n');
return fromDb;
}
console.info('Role does not exist - creating new \n');
const doc = new Role({role, ...defaults});
// All capabilities (per role) can be created/found in parallel
const roleCapabilities = await Promise.all(capabilities.map(findOrCreateCapability));
doc.capabilities = roleCapabilities.map(c => c._id);
await doc.save();
console.info('Role created: ', role);
console.info('');
return doc;
}
const findOrCreateCapability = async ({capability, ...defaults}) => {
console.info('Looking for capability: ', capability);
let doc = await Capability.findOne({capability}).exec();
if (doc) {
console.info(`Capability ${capability} found - using existing...`);
}
else {
console.info(`Capability ${capability} does not exist - creating new`);
doc = new Capability({capability, ...defaults});
await doc.save();
}
return doc;
}
seedDB()
.then(() => {
console.info('Exiting...: ');
process.exit(0);
})
.catch(error => {
console.error('Seed failed');
console.error(error);
process.exit(1);
})
We have a dictionary of capabilities and a list of roles that we can map to db operations.
The idea is that each role should contain the full definition of a capability, it can be used to either find the existing capability or create it if it doesn't exist
For each role in the list we make a query to see if it exists.
When it exists we do nothing and move to the next role
When it doesn't exist we have all the data needed to create it and create/find any capabilities that it might need
When you figure out all the roles and capabilities of the application you just put them in the ROLES_TO_SEED and CAPABILITIES static mappings
The script relies on the above mentioned middleware modifications in models
And a small bonus
You don't need many to many relationship to match capabilities to the roles they are used in. Here's how you can aggregate that information if only the Role model have an array of capabilities (refs). Run this after the database is seeded:
const showCapabilitiesUsages = async () => {
const result = await Capability.aggregate([
{
$lookup: {
from: 'roles',
let: {searched: '$_id'},
pipeline: [
{
$match: {
$expr: {
$in: ['$$searched', '$capabilities']
}
}
}
],
as: 'roles'
}
}, {
$project: {
_id: 0,
capability: 1,
description: 1,
usedInRoles: {
$map: {
input: '$roles',
as: 'role',
in: '$$role.role',
}
}
}
}
]).exec();
console.log('Aggregate result: ', result);
}
You should get a result like:
Aggregate result: [
{
capability: 'updateUsers',
description: 'Update the data of all users',
usedInRoles: [ 'admin' ]
},
{
capability: 'viewUsers',
description: 'View public data of users',
usedInRoles: [ 'admin', 'viewer' ]
},
{
capability: 'updateUserOwn',
description: 'Update user own data',
usedInRoles: [ 'viewer' ]
}
]
Try something like this, it should would work:
const roles = [
{
name: 'admin',
description: 'Administrator',
},
{
name: 'viewer',
description: 'Viewer',
},
];
const capabilities = [
// Capabilities
{
name: 'createCapability',
description: 'Create a new capability',
roles: ['admin'],
},
{
name: 'deleteCapability',
description: 'Delete a capability',
roles: ['admin'],
}
// Roles
{
name: 'createRole',
description: 'Create a new role',
roles: ['admin'],
},
{
name: 'deleteRole',
description: 'Delete a role',
roles: ['admin'],
},
// Users
{
name: 'updateUser',
description: 'Update current user data',
roles: ['viewer'],
},
{
name: 'updateUsers',
description: 'Update the data from any user',
roles: ['admin'],
},
];
const seedRoles = async (roles) => {
if (0 == roles.length || !Array.isArray(roles)) {
return;
}
console.log('');
for (const role of roles) {
const savedRole = await Role.findOneAndUpdate(
{name: role.name},
{$setOnInsert: role},
{upsert: true, new: true, rawResult: true},
);
if (!savedRole) {
console.log(`Role “${savedRole.value.name}” already on database.`);
} else {
console.log(`Role “${savedRole.value.name}” added to database.`);
}
}
};
const seedCapabilities = async (capabilities) => {
if (0 == capabilities.length || !Array.isArray(capabilities)) {
return;
}
console.log('');
for (const capability of capabilities) {
const rolesToPush = capability.roles;
delete capability.roles;
const addedCapability = await Capability.findOneAndUpdate(
{name: capability.name},
{$setOnInsert: capability},
{upsert: true, new: true, rawResult: true},
);
if (!addedCapability) {
console.log(
`Capability “${addedCapability.value.name}” ` +
`already on database.`,
);
} else {
console.log(
`Capability “${addedCapability.value.name}” ` +
`added to database.`,
);
if (rolesToPush && Array.isArray(rolesToPush)) {
rolesToPush.forEach(async (role) => {
const roleToPush = await Role.findOne({name: role});
if (roleToPush) {
roleToPush.capabilities.push(addedCapability.value);
await roleToPush.save();
}
});
}
}
}
};
const seedDb = async (roles, capabilities, users) => {
try {
await seedRoles(roles);
await seedCapabilities(capabilities);
console.log('roles', roles);
} catch (error) {
console.error(error);
}
};
module.exports = seedDb;

Normalizr - is it a way to generate IDs for non-ids entity model?

I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256

Categories

Resources