Related
I have a function on my server that is supposed to get a post by its ID. The function works up until the "foundPost" constant, where I can't seem to find one of the documents from the "posts" array. I've tried substituting findOne for find and the ObjectIds work for the const 'post'.
I've double checked that post_id is 62067c1211eea1531d5872f4
Here is the function to find a post:
const postById = async (req, res) => {
const userId = req.params.userId;
const post_id = req.params.post_id;
const posts = await Post.findOne({ user: userId });
console.log(posts); //see this below
const foundPost = await posts.findOne({ "upload": post_id }); //error here
console.log(foundPost);
return res.json({ success: true, Post: foundPost });
};
Here is what 'console.log(posts)' returns:
[
{
upload: new ObjectId("623b681bdf85df9086417723"),
edited: false,
title: 'Test 1',
description: 'testing post 1',
name: 'John ',
sharedPost: 0,
},
{
upload: new ObjectId("62067c1211eea1531d5872f4"),
edited: false,
title: 'Test 2',
description: 'testing post 2',
name: 'John ',
sharedPost: 0,
}
]
I'm hoping that the function will return:
{
success: true,
{
upload: new ObjectId("62067c1211eea1531d5872f4"),
edited: false,
title: 'Test 2',
description: 'testing post 2',
name: 'John ',
sharedPost: 0,
},
}
Can anyone see why the line const foundPost = await posts.findOne({ "upload": post_id }); isn't working?
Thank you for your help.
****** Response to answer ******
Hello, thanks a lot for your answer, unfortunately it's still giving an error. Please see below the model for the code I'm using:
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
post: [
{
user: {
type: Schema.Types.ObjectId,
ref: "user",
},
upload: {
type: Schema.Types.ObjectId,
ref: "upload",
},
title: {
type: String,
},
description: {
type: String,
},
},
],
date: {
type: Date,
default: Date.now,
},
});
I noticed that you used 'userProfile.posts' which I adapted to 'userProfile.post' to match this schema. I'm also not sure if you wanted to use 'subdoc' or 'subDoc' in line 11 of your code, but I tried both with the same error for each. I determined that the code stuck at the const subDocs = userPosts.filter(filter); line. I've looked into the .filter method you've used and can't find any potential errors. Not sure what the issue is.
Thanks
The issue is that you cannot run another mongo query on objects that were result of a previous query.
//will return a single document if found, or null if not found.
const posts = await Post.findOne({ user: userId });
//this will not work because at this point 'posts' will be either a Document or null value
//So the object will not have the method 'findOne' available.
const foundPost = await posts.findOne({ "upload": post_id });
The solution is to deal correctly with the types of objects you have.
Below is a functional and safe implementation that solves your issue:
const userPosts = await Post.findOne({ user: userId }).exec();
if (!userPosts) {
// document not found with provided userId
return res.json({ success: false });
}
//here we have a Document
//check if document has 'posts' property and is an array
if (userPosts.posts) {
//filter the posts array
const filter = function(subDoc) {
return subdoc.upload === post_id
}
const subDocs = userPosts.filter(filter);
//filter returns an array, so we must check if has itens
//then we grab the first item
if (subDocs.length > 0) {
const foundPost = subDocs[0];
return res.json({ success: true, Post: foundPost });
}
//subDoc not found, return correct response
return res.json({ success: false });
}
If your Post model schema is what I'm supposing to be, this code will work perfectly.
const schema = mongoose.schema({
user: Number,
posts: [{ upload: Number }]
})
In case of error, please add the code of the model schema structure.
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;
So I have a file that I use module exports on and it has 4 fields among which an execute field that takes 2 args and is essentially a function. It doesn't return anything instead it uses discord.js and runs this message.channel.send('Pong');. I want to test this using jest
How do I:
1 - Make sure that the message.channel.send was called with 'Pong' as args
2 - How do I mock it so it doesnt actually call it (i just want to make sure that the text inside of it, like the actual argument is 'Pong' since calling it won't work due to the lack of a proper message object)
I can access the actual command and execute it but I am unsure as to how to check the contents of message.channel.send. The message object cannot be reconstructed by me so that might also need mocking.
I'm using discord.js but that shouldn't really matter.
I will also have to test commands that feature functions that do have returns so how should I go about them?
You can try this:
const Discord = require('discord.js')
// replace this with whatever the execute command is
// e.g. const ping = require('./commands/ping').execute
const ping = async (message, args) => {
message.channel.send('Pong')
}
// a counter so that all the ids are unique
let count = 0
class Guild extends Discord.Guild {
constructor(client) {
super(client, {
// you don't need all of these but I just put them in to show you all the properties that Discord.js uses
id: count++,
name: '',
icon: null,
splash: null,
owner_id: '',
region: '',
afk_channel_id: null,
afk_timeout: 0,
verification_level: 0,
default_message_notifications: 0,
explicit_content_filter: 0,
roles: [],
emojis: [],
features: [],
mfa_level: 0,
application_id: null,
system_channel_flags: 0,
system_channel_id: null,
widget_enabled: false,
widget_channel_id: null
})
this.client.guilds.cache.set(this.id, this)
}
}
class TextChannel extends Discord.TextChannel {
constructor(guild) {
super(guild, {
id: count++,
type: 0
})
this.client.channels.cache.set(this.id, this)
}
// you can modify this for other things like attachments and embeds if you need
send(content) {
return this.client.actions.MessageCreate.handle({
id: count++,
type: 0,
channel_id: this.id,
content,
author: {
id: 'bot id',
username: 'bot username',
discriminator: '1234',
bot: true
},
pinned: false,
tts: false,
nonce: '',
embeds: [],
attachments: [],
timestamp: Date.now(),
edited_timestamp: null,
mentions: [],
mention_roles: [],
mention_everyone: false
})
}
}
class Message extends Discord.Message {
constructor(content, channel, author) {
super(channel.client, {
id: count++,
type: 0,
channel_id: channel.id,
content,
author,
pinned: false,
tts: false,
nonce: '',
embeds: [],
attachments: [],
timestamp: Date.now(),
edited_timestamp: null,
mentions: [],
mention_roles: [],
mention_everyone: false
}, channel)
}
}
const client = new Discord.Client()
const guild = new Guild(client)
const channel = new TextChannel(guild)
// the user that executes the commands
const user = {id: count++, username: 'username', discriminator: '1234'}
describe('ping', () => {
it('sends Pong', async () => {
await ping(new Message('ping', channel, user))
expect(channel.lastMessage.content).toBe('Pong')
})
})
You also need to put testEnvironment: 'node' in your jest configuration (see this issue).
Edit
You can also use Discord.SnowflakeUtil.generate() to generate an id if you need to obtain things like the timestamp.
How to pass parameters to the inquirer questions, so that i can set the values of a question object based on either values from previous questions or from code outside the prompt?
The only way i can see of achieving this if based on answer of a previous question is to nest the inquirer prompt calls
const inquirer = require('inquirer');
function getPath(){
return {
'system1':`system1path`,
'system2':`system2path`,
'system3':`system3path`
}
}
inquirer.prompt([
{
type: 'list',
name: 'testSystem',
message: 'Which system do you want to run?',
choices: ['system1', 'system2', 'system3']
},
{
type: 'fuzzypath',
name: 'searchTestSuite',
excludePath: nodePath => nodePath.startsWith('node_modules'),
itemType: 'any',
rootPath: getPath()['< answer from question(testSystem) >'],
message: 'Select a target directory :',
default: `system1path`,
suggestOnly: false,
depthLimit: 6,
},
]).then(answers => {
console.log(answers);
});
Expected result :
If you select testSystem = system2
You should get rootPath = system2Path , without nesting the inquirer prompts or by using whenfunction (since when seems to be dealing with boolean values)
You can solve it by nesting, but changing from Promise.then to async-await makes it more readable. Inquirer.js uses promises, hence you can use await to capture prompt answers and by issuing multiple prompts you can save the state between prompts. See code below.
PS: I've removed the default: ..., parameter from fuzzypath because it yields the default value despite it beign outside the root path.
const inquirer = require('inquirer');
inquirer.registerPrompt('fuzzypath', require('inquirer-fuzzy-path'))
const system1path = ...;
const system2path = ...;
const system3path = ...;
function getPath(){
return {
'system1': `${system1path}`,
'system2': `${system2path}`,
'system3': `${system3path}`
};
}
(async function () {
const {testSystem} = await inquirer.prompt({
type: 'list',
name: 'testSystem',
message: 'Which system do you want to run?',
choices: ['system1', 'system2', 'system3']
});
const {searchTestSuite} = await inquirer.prompt({
type: 'fuzzypath',
name: 'searchTestSuite',
excludePath: nodePath => nodePath.startsWith('node_modules'),
itemType: 'any',
rootPath: getPath()[testSystem],
message: 'Select a target directory :',
suggestOnly: false,
depthLimit: 6,
});
console.log({testSystem, searchTestSuite});
})();
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});
})