How to fix pending issue regarding mongoose with axios post - reactjs

I'm working on a person project that requires data from a redux form to be sent to an express server through an axios call. I've got the data from the client to the server by using body-parser but am having problems saving to the MongoDB using mongoose. Why am I getting a pending request on my post call?
<code>
// Client side axios post call
export const createSchedule = formValues => async (dispatch, getState) => {
const res = await axios.post("/schedule/create", {
data: {
userId: getState().auth.googleId,
title: formValues.title,
description: formValues.description
}
});
dispatch({ type: CREATE_SCHEDULE, payload: res });
};
</code>
<code>
// server side axios post call
module.exports = app => {
app.post("/schedule/create", async (req, res) => {
const schedule = new Schedule({
googleId: req.body.data.userId,
title: req.body.data.title,
description: req.body.data.description
}).save(function(err) {
if (err) console.log("saved failed.");
else console.log("saved");
});
done(null, schedule);
});
};
</code>
<code>
// Schedule schema for mongoose
const mongoose = require("mongoose");
const { Schema } = mongoose;
const scheduleSchema = new Schema(
{
googleId: String,
title: String,
description: String,
date: { type: Date, default: Date.now }
},
{ collection: "schedules" }
);
mongoose.model("schedules", scheduleSchema);
</code>
Pending results in client console
TypeError: Schedule is not a constructor error in server console.

It worked out by changing how I accessed the Schedule model in my api call. I previously had imported it locally instead of using the mongoose.model("schedules") line that I should've used.

Related

Why express server receives front end data as undefined?

I am currently working on social media mern stack react app. I am using node js and express as my backend services , also using mongoose to store my data and axios and redux thunk which connect the backend to the front end. Till now I had no issue recieving and sending data to the server. Right now I am trying to create search post get request ,base on a keyword the user entered. The issue with it, that when I am sending the keyword to the server instead of recieving the string it gets undefined value, like redux thunk not sending anything. I will be very thankful if someone could help me with that. I am watching the code over and over again and can't find out the reason for that.
My post controller class(I copied only the relevant function):
import express from "express";
const app = express();
import Post from "../model/PostModel.js";
import ErrorHandlng from "../utilities/ErrorHandling.js";
import bodyParser from "body-parser";
import catchAsync from "../utilities/CatchAsync.js";
import User from "../model/UserModel.js";
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
export const getPosts = catchAsync(async (req, res, next) => {
const data = req.body.keyword;
const page = parseInt(req.query.page || "0");
const PAGE_SIZE = 20;
const query = new RegExp(data, "i");
const total = await Post.countDocuments({});
const posts = await Post.find({ $or: [{ title: query }, { content: query }] })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * page);
if (!posts) {
return next(new ErrorHandlng("No posts were found", 400));
}
res.status(200).json({
status: "success",
data: {
totalPages: Math.ceil(total / PAGE_SIZE),
posts,
},
});
});
My api class(front end,copied only the calling for that specific get request):
import axios from "axios";
const baseURL = "http://localhost:8000";
axios.defaults.withCredentials = true;
const API = axios.create({
baseURL,
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export const getPostsByKeyword = (keyword, page) =>
API.get(`/post/getPostsByKey?page=${page}`, keyword);
Post slice class:
export const fetchPostsByKeyWord = createAsyncThunk(
"post/getKeyword",
async ({ keyword, page }, { fulfillWithValue, rejectWithValue }) => {
try {
const response = await api.getPostsByKeyword(keyword, page);
if (response.statusCode === "400") {
throw new Error("There are no available posts");
}
const fetchData = await response.data.data.posts;
const totalPages = await response.data.data.totalPages;
return fulfillWithValue({ fetchData, totalPages });
} catch (err) {
console.log(err.response.message);
}
}
);
const initialState = { status: "undefined" };
const PostSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {},
});
export const postActions = PostSlice.actions;
export default PostSlice;
Calling the backend:
dispatch(fetchPostsByKeyWord({ keyword, page }))
.unwrap()
.then((originalPromiseResults) => {
console.log("thte " + " " + originalPromiseResults.totalPages);
console.log("The data is" + originalPromiseResults.fetchData);
setTotalPages(originalPromiseResults.totalPages);
})
.catch((err) => {
console.log(err.message);
});
As you can see I have not copied the whole code, I copied only the parts that are relevants for the question.
Browsers cannot currently send GET requests with a request body. XMLHttpRequest (which Axios uses) will ignore it and fetch() will trigger an error.
See also HTTP GET with request body for extra discussion on why trying this might be a bad idea.
You should instead pass everything required in the query string, preferably via the params option so it is correctly encoded...
export const getPostsByKeyword = (keyword, page) =>
API.get("/post/getPostsByKey", { params: { page, keyword } });
and grab the data via req.query server-side.
const { page, keyword } = req.query;
With vanilla JS, you can use URLSearchParams to construct the query string...
const params = new URLSearchParams({ page, keyword });
// XHR
const xhr = new XMLHttpRequest();
xhr.open("GET", `/post/getPostsByKey?${params}`);
// Fetch
fetch(`/post/getPostsByKey?${params}`); // GET is the default method
Your Axios instance creation could also be a lot simpler...
Axios is usually quite good at setting the correct content-type header, you don't have to
Your Express app isn't doing any content-negotiation so you don't need to set the accept header
Unless you're actually using cookies (which it doesn't look like), you don't need credential support
const API = axios.create({ baseURL });

How to delete/remove from mongo database after a certain time of creating?

I'am having trouble figuring out how to delete a document from mongo DB after a timeout. Anyone can help me out with a simple way of doing it and maybe explain to me why this one is wrong? (it works, the document is deleted after some time , but I get a error message and the server stops)
this is the code I used written and also as a picture together with the terminal errormessage, I note, the document is deleted after the setTimeout runs out, but server stops:
documents are pretty simple consist of these:
server.js
import express from "express";
import cors from "cors";
import mongoose from "mongoose";
import shareRoutes from "./routes/shares.js";
const app = express();
app.use(cors());
app.get("/", (req, res) => {
res.json({ message: "API running..." });
res.end("");
});
app.use(express.json());
app.use("/radar", shareRoutes);
mongoose
.connect(
"mongodb+srv://<creditentials>#cluster0.dqlf2.mongodb.net/locations?retryWrites=true&w=majority",
{ useNewUrlParser: true },
{ useFindAndModify: false }
)
.then(() => {
app.listen(5000, () => {
"Server Running on port 5000";
});
})
.catch((err) => {
console.log(err);
});
shares.js for the route
import express from "express";
import {
createLocation,
getLocations,
} from "../controllers/shareController.js";
const router = express.Router();
// create location
router.post("/", createLocation);
// get Locations
router.get("/", getLocations);
export default router;
shareController.js
import express from "express";
import shareLocation from "../models/shareLocation.js";
const router = express.Router();
export const createLocation = async (req, res) => {
const { latitude, longitude, dateShared, timeShared, city, road } = req.body;
const newLocation = shareLocation({
latitude,
longitude,
dateShared,
timeShared,
city,
road,
});
try {
await newLocation.save();
res.status(201).json(newLocation);
setTimeout(() => {
(async () => {
try {
await shareLocation.findByIdAndRemove(newLocation._id);
res.json({ message: "Shared location deleted" });
} catch (error) {
res.status(409).json({ message: error });
}
})();
}, 30000);
} catch (error) {
res.status(409).json({ message: newLocation });
}
};
export const getLocations = async (req, res) => {
try {
const locations = await shareLocation.find({});
res.status(200).json(locations);
} catch (error) {
console.log(error);
res.status(409).json("Unable to fetch Locations");
}
};
export const deleteLocation = async (req, res) => {
const { id } = req.params;
try {
await shareLocation.findByIdAndRemove(id);
res.json({ message: "Shared location deleted" });
} catch (error) {
res.status(409).json({ message: error });
}
};
export default router;
shareLocations.js for the schema
import mongoose from "mongoose";
const hours = new Date().getHours().toLocaleString();
const minutes = new Date().getMinutes().toLocaleString();
const actualHours = hours.length < 2 ? "0" + hours : hours;
const actualMinutes = minutes.length < 2 ? "0" + minutes : minutes;
const locationSchema = mongoose.Schema({
timeShared: {
type: String,
default: actualHours + ":" + actualMinutes,
},
dateShared: {
type: String,
default: new Date().toDateString(),
},
latitude: {
type: String,
required: true,
},
longitude: {
type: String,
required: true,
},
city: {
type: String,
},
road: {
type: String,
},
});
export default mongoose.model("shareLocation", locationSchema);
I'll start with what is the "proper" solution, we'll take a look at what's wrong with the code after.
Mongo provides a built in way to remove documents after a certain time period, the "proper" way is to built a TTL index on the required field, and to specify after how many seconds you want that document deleted.
Mongo will then periodically check the index and clear documents when the time is up, this removes all kinds of levels of complexity from your app ( for example this timeout can easily cause a memory leak if too many calls are called in a short time window ).
A TTL index is created by using the simple createIndex syntax:
db.collection.createIndex( { "created_at": 1 }, { expireAfterSeconds: 30 } )
This will make documents expire 30 seconds after creation, you'll just have to add this timestamp to your code:
const newLocation = shareLocation({
latitude,
longitude,
dateShared,
timeShared,
city,
road,
created_at: new Date() // this new line is required
});
I can also tell you're using mongoose, then mongoose provides created_at field automatically if you set the Schema to include timestamps meaning your app can even ignore that.
Now what's wrong with your code?
It's simple, you first respond to the response in this line:
res.status(201).json(newLocation);
But then after a 30 second timeout you try to respond again, to the same response:
res.json({ message: "Shared location deleted" });
Node does not allow this behavior, you can only call set headers once ( which is called when responding ), I will not go into detail why as there are many stackoverflow answers (like this) that explain it.
Apart from the obvious issue that crashes your app, other issue's can arise from this code, as I mentioned before a memory leak can easily crash your app,
If your app restarts for whatever reason the locations that were "pending" in memory will not be cleared from the db, and more.
This is why it's recommended to let the DB handle the deletion.

How to push the data to firestore after stripe checkout session complete using React js frontend

I am trying to push the checkout basket data to firebase firestore after a stripe checkout session complete using react js node js. the checkout session successfully completed without error. However, there are NO user orders data being push to firebase firestore from the piece of code below:
const { error } = await stripe.redirectToCheckout({
sessionId
}).then(()=>{
db
.collection('users')
.doc(user?.uid)
.collection('orders')
.doc()
.set({
basket: basket,
// amount: paymentIntent.amount,
})
});
Below is the whole pieces of codes of backend and frontend
Functions/index.js
const functions = require("firebase-functions");
const express=require("express");
const cors=require("cors");
require('dotenv').config({ path: './.env' });
//API
const stripe=require("stripe")('sk_test_51KM...zIP');
//App config
const app=express();
var admin = require("firebase-admin");
var serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
//middlewares
app.use(cors({origin: true}));
app.use(express.json());
async function createCheckoutSession(req, res) {
const domainUrl = process.env.WEB_APP_URL;
const { line_items, customer_email } = req.body;
// check req body has line items and email
if (!line_items || !customer_email) {
return res.status(400).json({ error: 'missing required session parameters' });
}
let session;
try {
session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items,
customer_email,
success_url: `${domainUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
// success_url: '/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: `${domainUrl}/canceled`,
shipping_address_collection: { allowed_countries: ['GB', 'US'] }
});
res.status(200).json({ sessionId: session.id, });
} catch (error) {
console.log(error);
res.status(400).json({ error: 'an error occured, unable to create session'});
}}
app.get('/',(req, res)=>res.send('Hello World!'));
app.post('/create-checkout-session', createCheckoutSession);
exports.api=functions.https.onRequest(app);
src/strip-checkout.js
import React, { useContext, useState,useEffect } from 'react';
import { useStripe } from '#stripe/react-stripe-js';
import CheckoutProduct from '../CheckoutProduct';
import { useStateValue } from '../StateProvider';
import { db } from '../firebase';
import { fetchFromAPI } from '../helpers';
import "./stripe-checkout.css";
import { useHistory } from 'react-router-dom/cjs/react-router-dom.min';
const StripeCheckout = () => {
const history=useHistory();
const [{basket, user},dispatch]=useStateValue();
const [email, setEmail] = useState('');
const [processing,setProcessing]=useState("");
const stripe = useStripe();
const handleGuestCheckout = async (e) => {
e.preventDefault();
setProcessing(true);
const line_items = basket?.map(item => {
return {
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: item.price*100, // amount is in cents
product_data: {
name: item.title,
description: item.material
images: [item.image[0]],
}}}})
const response = await fetchFromAPI('create-checkout-session', {
body: { line_items, customer_email: user.email },
})
const { sessionId } = response;
const { error } = await stripe.redirectToCheckout({
sessionId
}).then(()=>{
db
.collection('users')
.doc(user?.uid)
.collection('orders')
.doc()
.set({
basket: basket,
})
});
console.log(sessionId);
if (error) {
console.log(error);
}}
return (
<form onSubmit={handleGuestCheckout}>
<div className='submit-btn'>
<button type='submit' >
<span>{processing ?<p>Processing</p>:
"Proceed to Checkout"}</span>
</button>
</div>
</form>
);
}
export default StripeCheckout;
Since there is no shown any error or warning, how to push the data to firestore after stripe checkout session complete in this case?
Stripe Checkout returns to either a success- or a cancel-URL.
It does not send data to both URLs, except the CHECKOUT_SESSION_ID you may add to it when defining these URLs.
The usual way to get data from Stripe Checkout is to use a Firebase Function Webhook. This Webhook is called by Stripe if a transactions is done, or upon failure etc. This Webhook stores the data in Firebase. I had the same problem and found no other, proper solution than using a Webhook.
https://stripe.com/docs/webhooks
There is also a Firebase Extension providing support for Stripe, including the Webhook. Basically. it will add some collections to Firestore, which you then can query.
https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments

Mongoose/Mongodb getting .deleteOne is not a function

When I click to delete a post, my console is saying TypeError: post.user.posts.deleteOne is not a function. It giving me this error after deleting.
const post = await Post.findByIdAndDelete(id).populate('user'); This code I am deleting the post from Post Schema
await post.user.posts.deleteOne(post)This code is to delete the post from the User Schema. I populated user and assigned it to post and then delete the user's post from this code, but I'm getting the error here.
Below is the controller code
export const deletePost = async (req, res) => {
const { id } = req.params;
try {
const post = await Post.findByIdAndDelete(id).populate('user');
await post.user.posts.deleteOne(post)
if (!post) {
return res.status(500).json({ message: "Unable To Delete" })
}
res.status(200).json({ message: "Deleted Successfully" })
} catch (error) {
console.log(error);
}
}
Client side delete request
const handleDeleteTrue = async () => {
try {
const { data } = await api.delete(`/post/${id}`)
console.log(data)
window.location.reload();
} catch (error) {
console.log(error.response.data.message);
}
};
User model schema
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unqie: true
},
password: {
type: String,
required: true,
minlength: 6
},
posts: [{ type: mongoose.Types.ObjectId, ref: "Post", required: true }]
});
export default mongoose.model('User', userSchema);
Im able to delete the post from the post model schema, but in this pic, which shows the user model schema, that same post that was deleted is not deleted here. This is the problem Im trying to solve.
What I can seem to understand in your function below is that you're trying to delete a single post and also checking if post exists first
export const deletePost = async (req, res) => {
const { id } = req.params;
try {
const post = await Post.findByIdAndDelete(id).populate('user');
await post.user.posts.deleteOne(post)
if (!post) {
return res.status(500).json({ message: "Unable To Delete" })
}
res.status(200).json({ message: "Deleted Successfully" })
catch (error) {
console.log(error);
}
}
I'd suggest you try this
export const deletePost = async (req, res) => {
const { id } = req.params;
try {
//check if document exists in mongoDB collection
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(500).json({ message: "Unable To Delete" }) }
await Post.deleteOne(id)
res.status(200).json({ message: "Deleted Successfully" })
catch (error) {
console.log(error);
}
}
I found out the answer. My user model schema for post was an array so I had to use $pull to delete it.
This is the code that worked for me
await post.user.posts.pull(post)
await post.user.save()
You can't use findOneAndDelete on populate to delete one object from an array. it doesn't work that way. Use This Instead.
const result = await User.findOneAndUpdate(
{ _id: Id },
{ $pull: { post:PostId } },
{ new: true }
);
You can find More on Pull and Push Operations on BlogLink

React.js fetch method on my api is not working?

I am trying to do a simple fetch request from a database hosted on a local server and also I am connected to mongodb in the backend, it seems like something is going wrong with my fetch method. I am getting Failed to load resource: net::ERR_SSL_PROTOCOL_ERROR
and Uncaught (in promise) TypeError: Failed to fetch
function App() {
const [state, setState]= useState({
track: '',
artist:'',
album:'',
year: 1990
}
)
// const [token, setToken] = useState('');
useEffect(() => {
async function getData(){
const track = await fetch('https://localhost:3001/api/music')
.then(res => res.json());
console.log(track)
setState(track)
// console.log(res)
}
getData();
},[])
also this is my route&controller functions
router.get('/', musicCtrl.index)
controller:
function index(req, res){
Music.find({}, function(err, music){
res.status(200).json(music)
})
}
mongo connection
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const musicSchema = new Schema({
track: String,
artist: String,
album: String,
year: Number
}, {timestamps:true});
module.exports = mongoose.model('Music', musicSchema);
any help please ?!
You are using HTTPS in the fetch url.
Change
const track = await fetch('https://localhost:3001/api/music')
to
const track = await fetch('http://localhost:3001/api/music')
The getData function is also mixing await and .then. Change it to :-
async function getData(){
const track = await fetch('https://localhost:3001/api/music')
console.log(track.json())
setState(track.json())
}

Resources