I am trying to launch a Microsoft authentication via Cypress following the authentication structure proposed by Juunas11 available here (demo on youtube).
However, the request sent to the microsoft authority falls in timeout at the first execution. The following ones are launched without any problem.
Would there be a better way to manage this Microsoft authentication?
Below, the whole of my code written in a step definition but which will be inserted later in a Cypress command.
import { sign, decode, JwtPayload } from 'jsonwebtoken';
Given('le conseiller {string}', () => {
let cachedTokenExpiryTime = new Date().getTime();
let cachedTokenResponse: any = null;
if (cachedTokenExpiryTime <= new Date().getTime()) {
cachedTokenResponse = null;
}
const { tenantId, clientId, clientSecret, apiScopes, username, password } = Cypress.env('loginMicrosoft');
const authority = `https://login.microsoftonline.com/${tenantId}`;
const environment = 'login.windows.net';
const buildAccountEntity = (
homeAccountId: string,
realm: string,
localAccountId: string,
clientInfo: object,
username: string,
name: string
) => {
return {
authorityType: "MSSTS",
clientInfo: sign(clientInfo, clientSecret).split('.')[1],
homeAccountId,
environment,
realm,
localAccountId,
username,
name
};
};
const buildIdTokenEntity = (homeAccountId: string, idToken: string, realm: string) => {
return {
credentialType: "IdToken",
homeAccountId,
environment,
clientId,
secret: idToken,
realm,
};
};
const buildAccessTokenEntity = (
homeAccountId: string,
accessToken: string,
expiresIn: number,
extExpiresIn: number,
realm: string,
scopes: string[]
) => {
const now = Math.floor(Date.now() / 1000);
return {
homeAccountId,
credentialType: 'AccessToken',
secret: accessToken,
cachedAt: now.toString(),
expiresOn: (now + expiresIn).toString(),
extendedExpiresOn: (now + extExpiresIn).toString(),
environment,
clientId,
realm,
target: scopes.map((s) => s.toLowerCase()).join(' '),
tokenType: 'Bearer'
};
};
const injectTokens = (tokenResponse: any) => {
const idToken: JwtPayload = decode(tokenResponse.access_token) as JwtPayload;
const localAccountId = idToken.oid || idToken.sid;
const realm = idToken.tid;
const homeAccountId = `${localAccountId}.${realm}`;
const name = idToken.name;
const clientInfo = {
"uid": localAccountId,
"utid": realm
}
const accountKey = `${homeAccountId}-${environment}-${realm}`;
const accountEntity = buildAccountEntity(
homeAccountId,
realm,
localAccountId,
clientInfo,
username,
name
);
const idTokenKey = `${homeAccountId}-${environment}-idtoken-${clientId}-${realm}--`;
const idTokenEntity = buildIdTokenEntity(
homeAccountId,
tokenResponse.id_token,
realm
);
const accessTokenKey = `${homeAccountId}-${environment}-accesstoken-${clientId}-${realm}-${apiScopes.join('')}-`;
const accessTokenEntity = buildAccessTokenEntity(
homeAccountId,
tokenResponse.access_token,
tokenResponse.expires_in,
tokenResponse.ext_expires_in,
realm,
apiScopes
);
const msalAccountCle = `msal.${clientId}.active-account`;
const msalAccountEntitee = localAccountId;
cy.window().then((win) => {
win.localStorage.setItem(accountKey, JSON.stringify(accountEntity));
win.localStorage.setItem(idTokenKey, JSON.stringify(idTokenEntity));
win.localStorage.setItem(accessTokenKey, JSON.stringify(accessTokenEntity));
win.localStorage.setItem(msalAccountCle, msalAccountEntitee);
})
};
const login = (cachedTokenResponse: any) => {
let tokenResponse: any = null;
let chainable: Cypress.Chainable = cy.visit('');
if (!cachedTokenResponse) {
chainable = chainable.request({
url: `${authority}/oauth2/v2.0/token`,
method: 'POST',
body: {
grant_type: 'password',
client_id: clientId,
client_secret: clientSecret,
scope: 'openid profile user.read',
username: username,
password: password,
},
timeout: 120000,
form: true,
log: true,
retryOnStatusCodeFailure: true,
retryOnNetworkFailure: true
});
} else {
chainable = chainable.then(() => {
return {
body: cachedTokenResponse,
};
});
}
chainable
.then((response) => {
console.log('réponse de microsoft : ', response.allRequestResponses);
injectTokens(response.body);
cy.window().then((win) => {
expect(win.localStorage.length).to.be.gte(0);
});
tokenResponse = response.body;
cy.getCookies().log;
})
.visit('')
.then(() => {
return tokenResponse;
})
.waitUntil(() => cy.get('.header-sub'));
return chainable;
};
login(cachedTokenResponse)
.then((tokenResponse) => {
cachedTokenResponse = tokenResponse;
cachedTokenExpiryTime = new Date().getTime() + 50 * 60 * 1000;
});
});
I have this code in my messageCreate event:
// MODULES
const Discord = require('discord.js');
const mongoose = require('mongoose');
const Levels = require('discord.js-leveling');
// FILES
const Guild = require('../../models/guild');
const config = require('../../files/config.json');
const swearwords = require("../../files/data.json");
const colors = require('../../files/colors.json');
// ERROR MESSAGE
const errorMain = new Discord.MessageEmbed()
.setDescription("There was an error!")
.setColor(colors.COLOR)
const addedDatabase = new Discord.MessageEmbed()
.setDescription("This server is now added to our database.")
.setColor(colors.COLOR)
module.exports = async (Discord, client, message) => {
if (!message.guild) return;
if (message.author.bot) return;
const settings = await Guild.findOne({
guildID: message.guild.id
}, (err, guild) => {
if (err) message.channel.send(errorMain);
if (!guild) {
const newGuild = new Guild({
_id: mongoose.Types.ObjectId(),
guildID: message.guild.id,
prefix: config.PREFIX,
logChannelID: String,
enableLog: false,
enableSwearFilter: true,
enableMusic: true,
enableLevel: true,
});
newGuild.save()
.catch(err => message.channel.send(errorMain));
return message.channel.send({embeds: [addedDatabase]}).then(m => m.delete({ timeout: 10000 }))
}
});
// VARIABLES
const IDGuild = message.guild.id;
const user = message.author;
const prefix = settings.prefix;
const swearFilterOn = settings.enableSwearFilter;
// LEVEL SYSTEM
if (settings.enableLevel === "true") {
const requiredXp = Levels.xpFor(parseInt(user.level) + 1)
const randomAmountOfXp = Math.floor(Math.random() * 29) + 1;
const hasLeveledUp = await Levels.appendXp(message.author.id, message.guild.id, randomAmountOfXp);
if (hasLeveledUp) {
const user = await Levels.fetch(message.author.id, message.guild.id);
const levelEmbed = new Discord.MessageEmbed()
.setTitle('New Level!')
.setColor(colors.COLOR)
.setDescription(`**GG** ${message.author}, you just leveled up to level **${user.level}**!\nContiune to chat to level up again.`)
const sendEmbed = await message.channel.send({embeds: [levelEmbed]});
}
}
// EXECUTE COMMAND AND SWEARFILTER
if (swearFilterOn === "true") {
var msg = message.content.toLowerCase();
for (let i = 0; i < swearwords["swearwords"].length; i++) {
if (msg.includes(swearwords["swearwords"][i])) {
message.delete();
return message.channel.send("Please do not swear.").then(msg => msg.delete({ timeout: 3000 }));
}
}
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/);
const cmd = args.shift().toLowerCase();
const command = client.commands.get(cmd) ||
client.commands.find(a => a.aliases && a.aliases.includes(cmd));;
if (command) command.execute(client, message, args, Discord)
} else {
if (!message.content.startsWith(prefix) || message.author.bot) return;
const args = message.content.slice(prefix.length).split(/ +/);
const cmd = args.shift().toLowerCase();
const command = client.commands.get(cmd) ||
client.commands.find(a => a.aliases && a.aliases.includes(cmd));;
if (command) command.execute(client, message, args, Discord)
}
}
And when i send a message right after the bot joines the discord, it gives the error that it cannot read properties of null (reading 'prefix').
The bot crashes, and when it's restarted it works because it has been added to the Database. So how can i fix that when it sends the first message it can still read 'prefix'. its probably a database issue.
I was told that settings.prefix probaly wasn't assinged a value, is there a way to fix this?
I'm using mongodb for my database.
You really should assign the prefix right after the bot joins new server with guildCreate event. It may cause some issues if your database is down etc. When you wait till the message event
I'm using Google Cloud Functions (GCF) to schedule posts on Instagram (an app similar to Hootsuite). I just integrated local notifications and I want to add the ID (generated automatically by my "notificationManager") to the post (data sent to Google Cloud).
Basically, the way that it works is the app sends the data to GCF and GCF is responsible for adding the elements to Firestore Database, but after many failed attempts, I cannot seem to figure out how to add an additional value. My goal is to add the element "notificationIdentifier", but for some reason, GCF won't register it (I can't even see it in the logs!).
Here's what's in GCF:
/* eslint-disable max-len */
/* eslint-disable */
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const FACEBOOK_GRAPH_API_VERSION = "v11.0";
const FACEBOOK_APP_ID = Undisclosed APP_ID;
const FACEBOOK_APP_SECRET = Undisclosed APP_SECRET;
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
admin.initializeApp();
const app = express();
const main = express();
const db = admin.firestore();
/**
* publish Instagram Media Object.
* #param {string} container_id instagram media object creation id.
* #param {string} accessToken user access token.
* #return {Promise} Returns container status
*/
async function getContainerStatus(container_id, accessToken) {
let status = "IN PROGRESS";
let response;
try {
response = await axios.get(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${container_id}`,
{
params: {
access_token: accessToken,
fields: "status_code",
},
}
);
} catch (error) {
console.log(error);
return "ERROR";
}
// console.log(response.data.status_code, "status");
status = response.data.status_code;
return status;
}
/**
* Get LongLive Token Expire in 60 days.
* #param {string} accessToken user access token.
* #return {Promise} Return Long Live token.
*/
function getLongLiveToken(accessToken) {
return new Promise((resolve, reject) => {
axios
.get(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/oauth/access_token`,
{
params: {
grant_type: "fb_exchange_token",
client_id: FACEBOOK_APP_ID,
client_secret: FACEBOOK_APP_SECRET,
fb_exchange_token: accessToken,
},
}
)
.then((response) => {
resolve(response.data.access_token);
})
.catch((error) => {
reject(error);
});
});
}
/**
* Get Facebook Pages.
* #param {string} accessToken user access token.
* #return {Promise} Returns the facebook pages result.
*/
function getFacebookPages(accessToken) {
return new Promise((resolve, reject) => {
axios
.get(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/me/accounts`,
{
params: {
access_token: accessToken,
},
}
)
.then((response) => {
const data = response.data.data;
resolve(data);
})
.catch((error) => {
reject(error);
});
});
}
/**
* Get Instagram Account From Facebook Pages.
* #param {string} accessToken user access token.
* #param {string} pageId Page ID.
* #return {Promise} Returns the Instagram Account Id.
*/
function getInstagramAccountId(accessToken, pageId) {
return new Promise((resolve, reject) => {
axios
.get(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${pageId}`,
{
params: {
access_token: accessToken,
fields: "instagram_business_account",
},
}
)
.then((response) => {
if (response.data.instagram_business_account) {
resolve({
id: response.data.instagram_business_account.id,
});
} else {
resolve({ error: "No instagram Business Account" });
}
})
.catch((error) => {
reject(error);
});
});
}
/**
* Get Facebook Profile.
* #param {string} accessToken user access token.
* #return {Promise} Returns the Facebook Account Profile.
*/
function getFacebookProfile(accessToken) {
return new Promise((resolve, reject) => {
axios
.get(`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/me`, {
params: {
access_token: accessToken,
fields: "name,picture,email",
},
})
.then((response) => {
resolve({
picture: response.data.picture.data.url,
name: response.data.name,
email: response.data.email,
facebookUserId: response.data.id,
});
})
.catch((error) => {
reject(error);
});
});
}
/**
* Get Instagram Profile.
* #param {string} accessToken user access token.
* #param {string} instagramAccountId Instagram Account ID.
* #return {Promise} Returns the Instagram Account Profile.
*/
function getInstagramProfile(accessToken, instagramAccountId) {
return new Promise((resolve, reject) => {
axios
.get(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${instagramAccountId}`,
{
params: {
access_token: accessToken,
fields: "name,username,profile_picture_url",
},
}
)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
}
/**
* Create Instagram Media Object.
* #param {string} accessToken user access token.
* #param {string} instagramAccountId Instagram Account ID.
* #param {string} caption caption.
* #param {string} mediaType Instagram Account ID.
* #param {string} mediaUrl media url
* #return {Promise} Returns created Instagram Media ID.
*/
function createInstagramMedia(
accessToken,
instagramAccountId,
caption,
mediaType,
mediaUrl,
tags,
) {
return new Promise((resolve, reject) => {
const params = {
access_token: accessToken,
caption: caption,
};
let user_tags = [];
if(tags != null) {
for (let i = 0; i < tags.length; i++) {
const user_tag = {
"username": tags[i],
"x": Math.random(),
"y": Math.random()
}
user_tags.push(user_tag);
}
}
if (mediaType == "PICTURE") {
params["image_url"] = mediaUrl;
if(tags != null) {
params["user_tags"] = user_tags;
}
} else if (mediaType == "VIDEO") {
params["video_url"] = mediaUrl;
params["media_type"] = "VIDEO";
} else {
reject({ message: "Unknow media!" });
return;
}
axios
.post(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${instagramAccountId}/media`,
params
)
.then(async (response) => {
const container_id = response.data.id;
let container_status = "IN_PROGRESS";
while (container_status == "IN_PROGRESS") {
container_status = await getContainerStatus(
container_id,
accessToken
);
console.log(container_status, "Container status");
}
// resolve(response.data);
if (container_status == "ERROR") {
reject({ error: "Container error!" });
} else {
resolve(response.data);
}
})
.catch((error) => {
console.log(error);
reject(error);
});
});
}
/**
* publish Instagram Media Object.
* #param {string} accessToken user access token.
* #param {string} instagramAcctId instagram media object creation id.
* #param {string} mediaObjectId instagram media object creation id.
* #return {Promise} Returns the Instagram Account Profile.
*/
function publishMedia(accessToken, instagramAcctId, mediaObjectId) {
console.log(accessToken, " --> ", instagramAcctId, " --> ", mediaObjectId);
return new Promise((resolve, reject) => {
axios
.post(
`https://graph.facebook.com/${FACEBOOK_GRAPH_API_VERSION}/${instagramAcctId}/media_publish`,
{
access_token: accessToken,
creation_id: mediaObjectId,
}
)
.then((response) => {
resolve(response.data);
})
.catch((error) => {
console.log(error);
reject(error);
});
});
}
/**
* Facebook Login with uuid
* body params {uuid, accessToken}
*/
app.post("/register", async (req, res) => {
const uuid = req.body.uuid;
const accessToken = req.body.accessToken;
const instagramAccts = [];
let longLiveToken, userData, pages;
try {
longLiveToken = await getLongLiveToken(accessToken);
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to create LongLive Token",
});
}
try {
userData = await getFacebookProfile(accessToken);
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to get Facebook profile",
});
}
try {
pages = await getFacebookPages(accessToken);
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to get Facebook pages",
});
}
if (pages.length > 0) {
for (let i = 0; i < pages.length; i++) {
try {
const data = await getInstagramAccountId(accessToken, pages[i].id);
if (!data.error) {
const igProfile = await getInstagramProfile(accessToken, data.id);
// const tags = await db
// .collection("tags")
// .where("tag", igProfile["username"])
// .get();
// if (tags.docs.length < 1) {
// await db.collection("tags").add({
// tag: igProfile["username"],
// });
// }
igProfile["isActive"] = true;
if (i == 0) {
igProfile["isPrimary"] = true;
} else {
igProfile["isPrimary"] = false;
}
instagramAccts.push(igProfile);
}
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to get Instagram accounts",
});
}
}
}
try {
await db.collection("users").doc(uuid).set({
longLiveToken: longLiveToken,
picture: userData.picture,
name: userData.name,
email: userData.email,
facebookUserId: userData.facebookUserId,
accessToken: accessToken,
uuid: uuid,
instagramAccts: instagramAccts,
});
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to save user info to firestore",
});
}
return res.status(200).json({
message: "Success",
instagramAccts: instagramAccts,
});
});
app.post("/getInstagramAccounts", async (req, res) => {
const uuid = req.body.uuid;
const user = await db.collection("users").doc(uuid).get();
const userData = user.data();
const accessToken = userData.longLiveToken;
const instagramAccts = [];
let pages;
try {
pages = await getFacebookPages(accessToken);
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to get Facebook pages",
});
}
if (pages.length > 0) {
for (let i = 0; i < pages.length; i++) {
try {
const data = await getInstagramAccountId(accessToken, pages[i].id);
if (!data.error) {
const igProfile = await getInstagramProfile(accessToken, data.id);
if (i == 0) {
igProfile["isActive"] = true;
} else {
igProfile["isActive"] = false;
}
instagramAccts.push(igProfile);
}
} catch (error) {
console.log(error);
return res.status(400).json({
error: "Failed to get Instagram accounts",
});
}
}
}
try {
await db
.collection("users")
.doc(uuid)
.update({ instagramAccts: instagramAccts });
} catch (error) {
console.log(error);
return res.status(500).json({
error: "Failed to update instagram accounts",
});
}
return res.status(200).json({ instagramAccts: instagramAccts });
});
/**
* Schedule Instagram Media Object to publish.
* body params {time, mediaType, uuid, media, instagramAcctId}
*/
app.post("/schedule", async (req, res) => {
const uuid = req.body.uuid;
const time = req.body.time;
const mediaType = req.body.mediaType;
const media = req.body.media; // url string array
const tags = req.body.tags; // array of strings
const longitude = req.body.longitude;
const latitude = req.body.latitude;
const instagramAcctId = req.body.instagramAcctId;
const caption = req.body.caption;
const thumbnail = req.body.thumbnail;
const timeStamp = req.body.timeStamp;
const notificationIdentifier = req.notificationIdentifier;
try {
const postData = await db.collection("posts").add({
uuid: uuid,
time: time,
mediaType: mediaType,
media: media,
instagramAcctId: instagramAcctId,
published: false,
caption: caption,
tags: tags ? tags : null,
thumbnail: thumbnail ? thumbnail : null,
longitude: longitude,
latitude: latitude,
timeStamp: timeStamp,
notificationIdentifier: notificationIdentifier ? notificationIdentifier : null,
});
const ref = await postData.get();
await db.collection("posts").doc(ref.id).update({ id: ref.id });
res.status(200).json({
message: "Success!",
});
} catch (error) {
console.log(error);
res.status(500).json(error.message);
}
});
app.post("/update-schedule", async (req, res) => {
const id = req.body.id;
const time = req.body.time;
const tags = req.body.tags; // array of strings
const longitude = req.body.longitude;
const latitude = req.body.latitude;
const instagramAcctId = req.body.instagramAcctId;
const caption = req.body.caption;
const timeStamp = req.body.timeStamp;
const notificationIdentifier = req.body.notificationIdentifier;
const updateData = {};
if (time) {
updateData["time"] = time;
}
if (tags) {
updateData["tags"] = tags;
}
if (longitude) {
updateData["longitude"] = longitude;
}
if (latitude) {
updateData["latitude"] = latitude;
}
if (instagramAcctId) {
updateData["instagramAcctId"] = instagramAcctId;
}
if (caption) {
updateData["caption"] = caption;
}
if (timeStamp) {
updateData["timeStamp"] = timeStamp;
}
if (notificationID) {
updateData["notificationIdentifier"] = notificationIdentifier;
}
try {
await db.collection("posts").doc(id).update(updateData);
res.status(200).json({
message: "Update success!",
});
} catch (error) {
console.log(error);
res.status(500).json(error.message);
}
});
app.post("/remove-schedule", async (req, res) => {
const id = req.body.id;
try {
const postData = await db.collection("posts").doc(id).get();
if (postData.exists) {
await db.collection("posts").doc(id).delete();
res.status(200).json({
message: "Remove success!",
});
} else {
res.status(400).json({
message: "There is no such post!",
});
}
} catch (error) {
console.log(error);
res.status(500).json(error.message);
}
});
app.post("/get-posts-by-date", async (req, res) => {
const uuid = req.body.uuid;
let posts_by_date = [];
try {
const posts = await db
.collection("posts")
.where("published", "==", false)
.where("uuid", "==", uuid)
.orderBy("time")
.get();
for (let i = 0; i < posts.docs.length; i++) {
const postData = posts.docs[i].data();
console.log(new Date(postData["time"]).toLocaleDateString());
const localDate = new Date(postData["time"]).toLocaleDateString();
if (posts_by_date[localDate]) {
posts_by_date[localDate].push(postData);
} else {
posts_by_date[localDate] = [];
posts_by_date[localDate].push(postData);
}
}
console.log(posts_by_date, "posts by date");
return res.status(200).json({
posts: posts_by_date,
});
} catch (error) {
console.log(error);
return res.status(500).json(error.message);
}
});
exports.scheduledFunction = functions.pubsub
.schedule("* * * * *")
.onRun((context) => {
console.log("This will be run every 1 minute!");
db.collection("posts")
.where("published", "==", false)
.get()
.then((querySnapshot) => {
querySnapshot.forEach(async (doc) => {
console.log(doc.id, " => ", doc.data());
const post = doc.data();
const now = new Date();
const publishDate = new Date(post.time);
if (publishDate <= now) {
//publish media object.
const uuid = post.uuid;
const userRef = await db.collection("users").doc(uuid).get();
const user = userRef.data();
const longLiveToken = user.longLiveToken;
const instagramAcctId = post.instagramAcctId;
const medias = post.media;
//const notificationIdentifier = post.notificationIdentifier;
for (let i = 0; i < medias.length; i++) {
try {
const mediaObjects = await createInstagramMedia(
longLiveToken,
instagramAcctId,
post.caption,
post.mediaType,
medias[i],
post.tags,
// notificationIdentifier
);
const mediaObjectId = mediaObjects.id;
await publishMedia(
longLiveToken,
instagramAcctId,
mediaObjectId
);
} catch (error) {
console.log(error);
return;
}
}
await db
.collection("posts")
.doc(doc.id)
.update({ published: true });
}
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
return null;
});
main.use("/v1", app);
main.use(bodyParser.json());
main.use(bodyParser.urlencoded({ extended: true }));
exports.api = functions.https.onRequest(main);
and this is how the data is sent:
private func schedulePost(uuid: String, time: Date, mediaType: String, media: [String], caption: String, tags:[String], location: CLLocation?, thumbImageUrl: String) {
var newCap: [String] = []
newCap.append(caption)
newCap.insert(contentsOf: tags, at: newCap.endIndex)
let newCaption = newCap.joined(separator: " ")
print (newCaption)
AuthManager.shared.loadUser()
guard let instagramAccountId = AuthManager.shared.currentUser?.id else {return}
let timeStamp = NSDate().timeIntervalSince1970
if !self.TrueStory {
StoryManager().addNewTask("POST", "Post", time, self.ImageURL)
print("Scheduled Post" + self.ImageURL)
// let notificationID = notificationIdentifier
//tags.insert(caption, at: tags.firstIndex)
let param = ["uuid": uuid, "time": time, "mediaType": mediaType, "media": media, "instagramAcctId": instagramAccountId, "caption": newCaption, "tags": "", "latitude": location?.coordinate.latitude ?? "", "longitude":location?.coordinate.longitude ?? "", "thumbnail":thumbImageUrl, "timeStamp":timeStamp, "notificationIdentifier":notificationIdentifier] as [String : Any]
print ("The PARAMS are: ")
print (param)
ServerApi.shared.scheduleIGPosts(param: param, success: {response in
print(response)
ProgressHUD.dismiss()
AppManager.shared.isPostScheduled = true
AppManager.shared.showNext()
// NotificationCenter.default.post(name: .PostWasSuccessfullyScheduled, object: nil, userInfo: ["posted": true])
}, failure: {(error) in
print(error)
self.showAlert(error.description)
ProgressHUD.dismiss()
})
} else if self.TrueStory {
StoryManager().addNewTask("STORY", "Story", time, self.ImageURL)
print("Scheduled Story" + self.ImageURL)
// let notificationID = notificationIdentifier
let param = ["uuid": uuid, "time": time, "mediaType": mediaType, "media": media, "instagramAcctId": instagramAccountId, "caption": caption, "tags": "", "latitude": location?.coordinate.latitude ?? "", "longitude":location?.coordinate.longitude ?? "", "thumbnail":thumbImageUrl, "timeStamp":timeStamp, "notificationIdentifier":notificationIdentifier] as [String : Any]
// "notificationID":notificationIdentifier
ServerApi.shared.scheduleIGPosts(param: param, success: {response in
print(response)
ProgressHUD.dismiss()
AppManager.shared.isPostScheduled = true
AppManager.shared.showNext()
// NotificationCenter.default.post(name: .PostWasSuccessfullyScheduled, object: nil, userInfo: ["posted": true])
}, failure: {(error) in
print(error)
self.showAlert(error.description)
ProgressHUD.dismiss()
})
}
}
And the "ScheduleIGPost" function for your entertainment:
import Foundation
import SwiftyJSON
import Alamofire
struct AppUrls {
static let baseUrl = URL that I wont disclose
static let registerIGAccounts = baseUrl + "register"
static let scheduleIGPosts = baseUrl + "schedule"
static let updateIGPosts = baseUrl + "update-schedule"
static let removeIGPosts = baseUrl + "remove-schedule"
}
class ServerApi {
static let shared = ServerApi()
func scheduleIGPosts(param: [String: Any], success: #escaping(JSON) -> Void, failure: #escaping(JSON) -> Void) {
ApiWrapper.requestPOSTURLWithoutToken(AppUrls.scheduleIGPosts, params: param, success: {(response) in
print(JSON(response))
success(JSON(response))
}, failure: { (error) in
let err = JSON(error)
print(err)
failure(err)
})
}
I did check and see if "param" included "notificationIdentifier" when sending to GCF and it does, but if I check the logs in GCF, I can see everything that's supposed to be except for "notificationIdentifier". And when I replace, for example, "tags" with the "notificationIdentifier", then it works.
I'm unsure if Firestore/Firebase has a limit of elements or not and why this behaviour is happening. To be honest, I couldn't find much information online.
Feel free to ask any questions if I was unclear, as I tend to be!
This was solved quite simply, the app is using a Cloud Function called "API" which is used before accessing the "Schedule" function. So changed the values (or rather added the values) and everything is working fine now.
const checkAuth = require("../../../util/checkAuth");
const { Post } = require("../../../models/post");
const { Like } = require("../../../models/like");
const upvoteComment = async (_, { username, postID, commentID }, context) => {
checkAuth(context);
/* TODO Securety concern one Person can upvote the same post via post request */
const like = new Like({
username: username,
createdAt: new Date(),
});
const post = await Post.findOne({ _id: postID });
const findComment = (post) => {
for (let i = 0; post.comments.length > i; i++) {
if (post.comments[i]._id == commentID) {
return i;
}`enter code here`
}
};
const index = findComment(post);
console.log(index, "index");
console.log(post.comments[0]);
post.comments[index].likes.push(like);
post.save();
return like;
};
module.exports = { upvoteComment };
I am trying to save a Like which I have modelled into a Comment on a Post. First I fetch the Post from my Mongo DB database, then I loop through the comments to find the comment with the id of the comment that was liked and I push the new like object into that comments likes array but when I save the like will not be saved... Does this have something to do with nesting?
So i changed the Code to this
const checkAuth = require("../../../util/checkAuth");
const { Post } = require("../../../models/post");
const { Like } = require("../../../models/like");
const upvoteComment = async (_, { username, postID, commentID }, context) => {
checkAuth(context);
/* TODO Securety concern one Person can upvote the same post via post request */
const like = new Like({
username: username,
createdAt: new Date(),
});
const post = await Post.findOne({ _id: postID });
console.log(post.comments);
const findComment = (post) => {
for (let i = 0; post.comments.length > i; i++) {
if (post.comments[i]._id == commentID) {
return i;
}
}
return -1;
};
const index = findComment(post);
/* console.log(index, "index");
console.log(post.comments[index]);
*/
if (index !== -1) {
post.comments[index].likes.push(username);
post.comments[index].likes.push(like);
post.title="this title will be saved"
const res = await post.save();
console.log("likes on the server: ", res.comments[index].likes);
}
const savedPost = await Post.findOne({ _id: postID });
console.log("likes on Database: ", savedPost.comments[index].likes);
return like;
};
module.exports = { upvoteComment };
and the console will return this:
likes on the server: [
'username',
{
_id: 6081838453f4aa1ff4db6b5e,
username: 'username',
createdAt: 2021-04-22T14:09:08.817Z
}
]
likes on Database: []
This does indeed change the title of the post so I have concluded that mongoDB sees the post.comment[i].likes field as uneditable even though i have declared it as an Array... The type of item doesnt matter for this either as i have tested it in this example.
Try this once hope it works for you.
const upvoteComment = async (_, { username, postID, commentID }, context) => {
checkAuth(context);
/* TODO Securety concern one Person can upvote the same post via post request */
const like = new Like({
username: username,
createdAt: new Date(),
});
const post = await Post.findOne({ _id: postID });
const findComment = (post) => {
for (let i = 0; post.comments.length > i; i++) {
if (post.comments[i]._id == commentID) {
return i;
}
}
return -1;
};
const index = findComment(post);
console.log(index, "index");
console.log(post.comments[0]);
if(index !== -1)
post.comments[index].likes.push(like);
await post.save();
return like;
};
Try using
post.comments[i].likes.push({
username: "johndoe",
createdAt: new Date(),
});
instead of using new Like({...})
By the way, you should use findIndex for conciseness and clarity:
...
const i = post.comments.findIndex(comment => comment._id === commentID);
post.comments[i].likes.push(like);
await post.save();
...
So the Problem lied within my Schema definiotion and was a simple Typo I apologize to everyone that tried to wrap their head around this...
Auth.js (CONTROLLER):
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const keys = require('../config/keys')
const errorHandler = require('../utils/errorHandler')
// REGISTRATION
module.exports.register = async function(req, res) {
const candidate = await User.findOne({email: req.body.email})
const phone = await User.findOne({phone: req.body.phone})
if (candidate) {
// user exist
res.status(409).json({
message: 'Email is taken. Try another one!'
})
} else {
// new user
const salt = bcrypt.genSaltSync(10)
const password = req.body.password
const user = new User({
username: req.body.username,
password: bcrypt.hashSync(password, salt),
})
try {
await user.save()
res.status(201).json(user)
} catch(e) {
errorHandler(res, e)
}
}
}
Auth.js (ROUTES):
const express = require('express')
const controller = require('../controllers/auth')
const router = express.Router()
//localhost:5000/api/auth/register
router.post('/register', controller.register)
module.exports = router
User.js(MODEL):
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
username: {
type: String,
unique: false
},
password: {
type: String,
required: true
},
codeRef: {
type: String,
default: makeid(6).toString(),
unique: true
}
})
// GENERATE RANDOM REFFERAL LINK
function makeid(length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var charactersLength = characters.length
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
module.exports = mongoose.model('users', userSchema)
Everything works fine, the new user added successfully.
The problem comes with another added user after the first one. Generated referral code gives error until we refresh the MongoDB database or restart the server (NPM).
(Results from POSTMAN)
{
"success": false,
"message": "E11000 duplicate key error collection: Cluster.users index: codeRef_1 dup key: { codeRef: \"DDP1SF\" }"
}
Logs from console:
POST /api/auth/register 201 457.563 ms - 350
POST /api/auth/register 500 304.148 ms - 142
POST /api/auth/register 500 190.155 ms - 142
Tried to change the const to var... Should I clear the Schema data of previous request somehow? Because my generated random code for 6 symbols is the same until refresh.
MODELS should NOT contain functions because they are static.
Placing the function inside CONTROLLER file did the work.
// GENERATE RANDOM REFFERAL LINK
function makeid(length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var charactersLength = characters.length
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
To show the response after the server was updated, use new: true :
const user = new User({
username: req.body.username,
password: bcrypt.hashSync(password, salt),
codeRef: makeid(6).toString(),
new: true
})