Mongoose - Discord saving channels in arrays - javascript

So I wanna clear it in first that I am a beginner in mongoose. So I wanted a help regarding saving arrays in the database. I want to relate this to Discord. I was building a bot where I want to store the ids of the channels to make the bot respond on them only. Earlier I was using Enmap, but transferred to mongoose for ease. So I don't know how to store arrays then push another string inside that array. Please help me out in this situation 😥

It's just so easy, after creating your model aka schema, you just set the type to array
example:
const mongoose = require('mongoose')
const schema = mongoose.Schema({
GuildId: {
type: String
required: true
},
ChannelIds: {
type: Array,
default: []
}
})
module.exports = mongoose.model("the_model_name", schema)
Alright now in your command you will need to get the data from the db
example:
// first you will need to define your schema
const schema = require('schema path')
// now we will define data and we will use try catch to not get any errors
let data;
try {
// Put here the guild id such as the message.guild.id, or anything that is unique for that data
data = await schema.findOne({ GuildId: ## })
// if there is no data we want to create a schema for it
if(!data) {
data = await schema.create({ GuildId: ## })
}
} catch(e) {
// if there is any error log it
console.log(e)
}
Now after we got the data we just push the channel id, to add it you just need to push it.
example
data.ChannelIds.push(/*the channel id or whatever you want to push*/)
last but not least just save the data
example
await data.save()

Related

TypeOrm: handle .save() that is called twice at the exact same time in an express API context

I have a getUser function that return a User entity after being saved into the database for caching reasons. Here is the pseudo-code for it:
export const getUser = async (userId: string): Promise<User> => {
if (userStoredInDatabase) return userStoredInDatabase // <- pseudo code
// ...if no user stored -> fetch external services to get user name, avatar, ...
const user = new User()
user.id = userId // <- PRIMARY KEY
// ...add data to the user
return getManager().save(user)
}
I use this function in 2 distinct routes of a simple expressjs API:
app.get('/users/:userId/profile', async (req, res) => {
const user = await getUser(req.params.userId)
// ...
})
app.get('/users/:userId/profile-small', async (req, res) => {
const user = await getUser(req.params.userId)
// ...
})
So far so good until I came to the problem that my frontend need to fetch /users/:userId/profile and /users/:userId/profile-small at the exact same time to show the 2 profiles. If the user is not yet cached in the database, the .save(user) will be called twice almost at the same time and I will respond for one of the 2 with an error due to an invalid sql insertion as the given user.id already exists.
I know I could just delay one of the request to make it work good enough but I'm not in the favor of this hack.
Do you have any idea how to concurrently .save() a User even if it is called at the same time from 2 different contexts so that TypeOrm knows for one of the 2 calls that user.id already exist and therefore do an update instead of an insert?
Using delay will not solve the prolm since the user can open 3 tabs at the same time. Concurency has to be handled.
Catch if there is a primary key violation (Someone already has stored the user between the get user and persist user code block)
Here is an example:
export const getUser = async (userId: string): Promise<User> => {
try {
if (userStoredInDatabase) return userStoredInDatabase // <- pseudo code
// ...if no user stored -> fetch external services to get user name, avatar, ...
const user = new User()
user.id = userId // <- PRIMARY KEY
// ...add data to the user
return getManager().save(user)
} catch (e) {
const isDuplicatePrimaryKey = //identify it is a duplicate key based on e prop,this may differ depending on the SQL Engine that you use. Debugging will help
if (isDuplicatePrimaryKey) {
return await //load user from db
}
throw e; // since e is not a duplicate primary key, the issue is somewhere else
}
}

Fetch all of the Discord threads on a server (Discord.js)

I'm starting out in Discord.js and trying to make a bot that prints all of the thread data from the server to the console. For all threads on the server, I basically want it to print just the name of the thread, the member who created the thread, and the timestamp it was made.
Previously I was working on code for one that prints thread entries from the audit log, but because that data deletes after 45 days, I'm looking to make a more efficient strategy to print all threads that have ever been made since the beginning of the server (or at least this year).
I found this post on fetching all channel ids for the server, and that code works for me, but when I try to convert that code to find data on threads, I'm struggling to figure out how to do that.
Does anyone have any suggestions on how I could approach this?
EDIT 4:
The code is now working to print some thread data, but I can't print more than 100 entries. I found this post that might help with pulling that data, but I need to convert it to threads rather than messages to use that.
With my code to print using cached data, it only prints 5-10, but if I pull from the audit log, I'm able to print up to 100. I still don't think that's the method I want though because it will delete after 45 days and I'd like to at least pull all data one time, then from there I can use this command to just pull less data after the initial pull if I do it more frequently.
Here's my current code:
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
client.on('ready', () =>{
console.log(`${client.user.tag}` + ' is online!');
})
// should be more specific with message, (so it can be like "!audit")
client.on('messageCreate', async function (message) {
const fetchedLogs = await message.guild.fetchAuditLogs({
limit: 100,
type: 110, // THREAD_CREATE
action_type: 110 // THREAD_CREATE
});
//const fetchedChannels = await message.guild.channels.fetch();
// Checks which command was done; message.content gets the message that the user sent to trigger this:
if(message.content === "!test"){
// get the guild
const guild = client.guilds.cache.get("694998889358557297");
// filter all the channels
const threads = guild.channels.cache.sort((a, b) => a.createdAt - b.createdAt).filter(x => x.isThread());
// Title of the Thread
const threadTitle = threads.map(info => `${info.name}`);
// ID of the Thread Creator
const threadUser = threads.map(info => `${info.ownerId}`);
// Date the Thread was Created
const threadDate = threads.map(info => `${info.createdAt}`);
// ALL INFO, if you want an array just remove the ".join()"
const threadInfo = threads.map(info => `Name: ${info.name}\nCreator: ${info.ownerId}\nCreated at: ${info.createdAt}\n`).join("");
console.log(threadTitle);
//console.log(threadUser);
//console.log(threadDate);
//console.log(threadInfo);
}
else {
console.log ("target doesn't exist");
}
});
Threads are just channels that are parented to another channel, so you can fetch them all the same way as normal channels and then filter the result to only include threads.
const threads = channels.cache.filter(x => x.isThread());
This will result in the threads variable being an array of threads for the guild.
You can just filter through all the guild's channels and find the ones that are a thread.
// get the guild
const guild = client.guilds.cache.get("guild ID");
// filter all the channels
const threads = guild.channels.cache.filter(x => x.isThread());
Then, you can map the information that you need:
// if you want an array just remove the ".join()"
const threadInfo = threads.map(info => `Name: ${info.name}\nCreator: ${info.ownerId}\nCreated at: ${info.createdAt}\n`).join("");
constole.log(threadInfo);

delete operator not functioning as expected in node.js

In my node.js express application I am retrieving a user from my database
const newUser = await User.create({
username,
password,
email,
avatar,
})
but before sending the response with the user object I want to remove the password.
delete newUser.password;
return res.status(200).json({ token, user: newUser });
but in my response the password is returned.
console.log(JSON.stringify(newUser))
returns:
{"_id":"11111111","username":"dylan","email":"dylan#email.com","admin":true,"password":"******"}
query return value is document not javascript object
Documents have a toObject method which converts the mongoose document into a plain JavaScript object.
first convert it to object and then use delete on each property you want
also with mongoose it can done more properly and automatically
User.methods.toJSON = function () {
const user = this;
const userObj = user.toObject();
delete userObj.password;
return userObj;
};
every time you send this document as response it convert to JSON and every time that JSON.strigify() is call on a document it call this toJSON() method
Maybe you can also do
delete newUser._doc.password

Firebase and Algolia secured search no search hits

I'm having some issues implementing a secured search with Firebase and Algolia.
I've been following the guide at https://firebase.google.com/docs/firestore/solutions/search -> Adding security
My problem is that everything seems to be working fine, however I do not receive any search hits.
When a user searches for an item, it invokes a function which first hits a firebase function to generate a Secured API Key, which also includes the security filter for the queries.
The firebase function:
app.use(getFirebaseUser);
app.get('/', (req, res) => {
// #ts-ignore
const uid = req.user.uid;
// Create the params object as described in the Algolia documentation:
// https://www.algolia.com/doc/guides/security/api-keys/#generating-api-keys
const params = {
// This filter ensures that only documents where author == uid will be readable
filters: `ownerID:${uid}`,
// We also proxy the uid as a unique token for this key.
userToken: uid,
};
// Call the Algolia API to generate a unique key based on our search key
const key = client.generateSecuredApiKey(ALGOLIA_SEARCH_KEY, params);
// Then return this key as {key: '...key'}
res.json({key});
});
This is literally copy/pasted from the firebase example, except I've replaced "author" with "ownerID", which is the term I want to match in my indices.
The getFirebaseUser() is an exact copy/paste from the official firebase example.
Once the client has received the secured API key, it get passed to algolia with the query:
const getSearchResults = async (query: string) => {
const index = client.initIndex('things');
try {
const userToken = await auth.currentUser?.getIdToken();
const response = await fetch(GOOGLE_URL, {
headers: { Authorization: `Bearer ${userToken}` },
});
const data = await response.json();
const client = algoliasearch(ALGOLIA_APP_ID, data.key);
const responses = await index.search(query);
console.log(responses);
} catch (err) {
console.error(err.message);
}
};
This is also the same function as the example, except written with async/await. (I did try an exact copy/paste, and the result is the same).
Also, the example shows index.search({query}) where the query string gets passed in as an object. This does not work either, and Typescript states that a string is expected.
If I try to change the initIndex to an index that doesn't exist, I get an error stating that it doesn't exist. This tells me the connection between my app and algolia is working as expected.
The returned object looks like this:
exhaustiveNbHits: true
hits: []
hitsPerPage: 20
nbHits: 0
nbPages: 0
page: 0
params: "query=test&filters=ownerID%3AKK3oXEkrIKb31BECDSJPMdgvTBz2&userToken=KK3oXEkrIKb31BECDSJPMdgvTBz2"
processingTimeMS: 1
query: "test"
If I do a regular search:
const getSearchResults = async (query: string) => {
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY);
const index = client.initIndex('things');
const responses = await index.search(query);
console.log(responses);
};
I do get hits:
exhaustiveNbHits: true
hits: Array(1)
0: {productName: "test ja", purchaseDate: "15.03.2021", purchasedFrom: "ok", ownerID: "KK3oXEkrIKb31BECDSJPMdgvTBz2", objectID: "25BadjQxUjRjkjH9ZnwW", …}
length: 1
__proto__: Array(0)
hitsPerPage: 20
nbHits: 1
nbPages: 1
page: 0
params: "query=test"
processingTimeMS: 1
query: "test"
This is literally bypassing the whole cloud function, which means the problem must be with generating the Secured API Key, the parameters or something.
The index data stored in Algolia looks like this:
objectID "25BadjQxUjRjkjH9ZnwW"
productName "test ja"
purchaseDate "15.03.2021"
purchasedFrom "ok"
ownerID "KK3oXEkrIKb31BECDSJPMdgvTBz2"
Which means I should get hits on ownerID, which I have in the cloud function.
EDIT:
When setting the filter from the parameter object to an empty string, I receive search hits. There might be something wrong with the way the filter is implemented.
Thanks for looking into this. I would appreciate all the help I can get!
Stephan Valois
I was in contact with algolia, and figured out what was wrong.
There was nothing wrong with the code, or the way the search was set up.
One thing that wasn't mentioned in the documentation is that the attribute I was matching (ownerID) wasn't declared as an attribute for faceting.
By going to Configuration -> Facets in the Algolia app, and adding the object key there, everything works as expected. (Sat it as 'not searchable'). This attribute is not supposed to be searchable. It should only be used to match the index to the correct user.
If anyone is using Algolia for secure search, whether or not you're using a Firebase function or another backend, the attribute in your filter must be set in Facets.

Create a new field inside a JSON

I'm using the combo Express (Node.js) and Mongoose to make a REST API. I'm trying to make the login using a JWT token but I've got a problem. When I execute the following code
const mongoose = require('mongoose');
const User = mongoose.model('User');
// other code
_api.post('/login', function (req, res) {
const data = req.body;
// some data control
User.findOne({ username: data.username}, function(err, doc) {
if (hash(password) == doc.password) { // password check
myToken = generateToken(); // generating the token
doc.jwtToken = myToken; // including the generated token to the response
res.status(200).json(doc); // return the final JSON to client
}
}
}
the final JSON returned by the API doesn't have the field "jwtToken":"mygeneratedtoken" and this is strange. I included other times new fields inside a JSON with the same syntax and it worked. I tried to use a tmp variable to which I assigned the doc content (that is a javascript object) and then I added the jwtToken filed and return the tmp variable. But nothing.
Can someone explain me if there is something wrong with my code or if there is something that I need to know?
Documents returned by mongoose are immutable, and thus assignment to doc.jwtToken does not modify the object. You can either use the lean method to modify the query, or toObject to convert the document to a regular javascript object. Try:
var docObject = doc.toObject();
docObject.jwtToken = myToken;
res.status(200).json(docObject);

Categories

Resources