Allow user to see all of their posts using mongoose / nextjs / react - reactjs

I am trying to create a my-posts page which will consist of all the posts made by the user.
User Schema
const userSchema = new Mongoose.Schema({
email: { type: String},
password: { type: String},
name: { type: String},
createdAt: { type: String},
posts: [{ type: Mongoose.Schema.Types.ObjectId, ref: 'Post'}]
},{
timestamps: true,
});
Posts Schema
const postSchema = new Mongoose.Schema({
name: { type: String},
category: { type: String},
userId: {type: Mongoose.Schema.Types.ObjectId, ref: "User"},
},{
timestamps: true
});
Creating a new post
handler.post(async(req, res) => {
await db.connect();
const newPost = new Post({
name: req.body.name,
category: req.body.category,
userId: req.body.userId
})
const userBy = await User.findById(userId)
const thePost = await newPost.save();
userBy.posts = user.posts.concat(post._id)
await user.save()
await db.disconnect();
});
export default handler;
Retrieve 'My Posts'
export async function getServerSideProps( {query} ) {
await db.connect();
const data = await User.findById(req.query.id).populate("vehicles").lean
await database.disconnect();
const userPosts = data.map(database.convertObj)
return {
props: {
userPosts
}
}
}
I'm not too sure how to pass the current logged in users _id to the getServerSideProps to then query the database for all the posts attached to that user in the posts array. If there is a better way to approach this please let me know or if you know what I am currently doing wrong, thanks.

This is an area where you might want to use a request from the client side to get the data, as suggested by the Nextjs docs rather than using getServerSideProps.
But if you really want to do it with SSR, you can use getServerSideProps and pass in data (like the userId) to getServerSideProps through the context parameter as either a query or a parameter.
If you want to do it as a query, you can get query as a prop, as you are already doing, or you can do it using a param like in this codesandbox example

Related

AWS Amplify and Next.JS with GraphQL Server Error No current user from getStaticPaths

I'm having trouble accessing data from Amplify's API Graphql, and it keeps returning
Server Error
Error: No current user
I've been following this tutorial: https://youtu.be/13nYLmjZ0Ys?t=2292
I know I'm signed into Amplify because if I go into different pages, I can grab user Auth and I can even display the SignOut button. But for whatever reason, I'm not sure why I'm getting this error
import { API } from "aws-amplify";
import { useRouter } from "next/router";
import { listActivations, getActivation } from "../../graphql/queries";
const Activation = ({ activation }) => {
const router = useRouter();
if (router.isFallback) {
return <div>Loading</div>;
}
return <div>{activation.title}</div>;
};
export default Activation;
export async function getStaticPaths() {
const SSR = withSSRContext();
console.log("static paths");
const activationData = await SSR.API.graphql({
query: listActivations,
});
console.log("activationData", activationData);
const paths = activationData.data.listActivations.items.map((activation) => ({
params: { id: activation.id },
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
const SSR = withSSRContext(); // added SSR, but still getting error
console.log("static props");
const { id } = params;
const activationData = await SSR.API.graphql({
query: getActivation,
variables: { id },
});
return {
props: {
activation: activationData.data.getActivation,
},
};
}
The console log static paths appears, and then after that, I get errors.
Do you think it has anything to do with my GraphQL schema?
type User #model #auth(rules: [{ allow: owner, ownerField: "username" }]) {
id: ID!
username: String!
email: String!
userType: UserType
}
type Activation
#model
#key(
name: "activationsByStudentId"
fields: ["student"]
queryField: "activationsByStudentId"
)
#auth(
rules: [
{ allow: groups, groups: ["Admin"] }
{ allow: owner }
{
allow: owner
ownerField: "studentId"
operations: [create, update, delete]
}
{ allow: private, operations: [read] }
{ allow: public, operations: [read] }
]
) {
id: ID!
studentId: ID!
title: String!
student: Student #connection(fields: ["studentId"])
teachers: [TeachersActivations] #connection(name: "ActivationTeachers")
}
Edit: I've also added User model to see if this could be a cause too.
Since both getStaticProps and getStaticPaths are called during build time, and on the server when fallback is equal to true, you need to configure Amplify for SSR (Server-Side Rendering). Make sure to take a look at SSR Support for AWS Amplify JavaScript Libraries.
The solution: first, configure Amplify for SSR:
Amplify.configure({ ...awsExports, ssr: true });
Then you need to use withSSRContext, and add the the authMode parameter. As quoted from the link above:
For example, take an AppSync GraphQL API that is backed by an identity provider such as Amazon Cognito User pools, Okto, or Auth0. Some GraphQL types may require a user to be authenticated to perform certain requests. Using the API class, the user identity will now automatically be configured and passed into the API request headers:
const SSR = withSSRContext();
const activationData = await SSR.API.graphql({
query: listActivations,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
Still, I couldn't figure out the issue why this can't work, so I decided to move my query into client-side
const [activation, setActivation] = useState(null);
const router = useRouter();
const { aid } = router.query;
useEffect(() => {
if (!aid) return;
async function activationDataFromClient() {
try {
const getActivationData = await API.graphql({
query: getActivation,
variables: {
id: aid,
},
});
setActivation(getActivationData.data.getActivation);
} catch (err) {
console.log("error fetching activation data: ", err);
}
}
activationDataFromClient();
}, [aid]);
I had the same problem. Changing the authMode to 'API_KEY' enabled it to work for me. See example below:
export async function getStaticPaths(context) {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({
query: listArticles,
authMode: 'API_KEY'
});
const paths = data.listArticles.items.map((article) => ({
params: { id: article.id },
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
const SSR = withSSRContext();
const { data } = await SSR.API.graphql({
query: getArticle,
variables: {
id: params.id,
},
authMode: 'API_KEY'
});
return {
props: {
article: data.getArticle
}
}
}

React useMutation doesn't pass the correct parameters input

I'm trying to execute a Mutation in a project, where I have Appollo GraphQL as middleware and reactJS as a front-end.
I have the following schema (the idea is to send a contact form):
input entryIntput {
url_title: String!
title: String!
channel_id: Int!
entry_date: String!
name: String!,
motivation: String!
mail: String!
job_position: String
}
...
type Mutation {
createEntry(input: entryIntput): postEntrySuccess
}
Resolvers:
Mutation: {
createEntry: (_, { input }) => channelEntriesService.postEntry(input)
}
ChannelEntriesService:
url_title: 'Application Form Entry-',
title: 'Application Form Entry #',
entry_date: Date.now(),
channel_id: 3
}) {
const auth = await authenticate.auth()
const nextEntry = await this.getNextEntry();
const patch = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
url: `${this._options.url}/create_channel_entry`,
data: `channel_id=${entry.channel_id}&url_title=${entry.url_title}${nextEntry}&title=${entry.title} ${nextEntry}&name=${entry.name}&motivation=${entry.motivation}&job_position=${entry.job_position}&mail=${entry.mail}&entry_date=${entry.entry_date}&session_id=${auth.session_id}`
}
const response = await axios(patch);
return response.data;
}
I have the Queue execution via useMutation and declaration like:
const APPLY = gql`
mutation PostEntry($input: entryIntput) {
createEntry(input: $input) {
entry_id
}
}
`;
....
const [createEntry, { error: formError, loading: formLoading }] = useMutation(APPLY);
On the front-end, where I'm using React-Hook-Form:
const onSubmit = (applicantData) =>
createEntry({
variables: {
url_title: applicantData?.name?.trim(),
entry_date: Date.now(),
title: applicantData?.name,
...applicantData,
},
});
When I execute the submit I've got undefined for the entry param in my channelEntries
.postEntry(),
While I execute it through a mutation from the apollo studio like:
mutation applicationEntryForm($entryInput: entryIntput) {
createEntry(input: $entryInput) {
entry_id
}
}
query_variables:
{
"entryInput": {
"channel_id": 3,
"url_title": "my First Entry input",
"entry_date": "1623829177995",
"title": "Voila intruder!",
"name": "Vlad",
"job_position": "cleaner",
"motivation": "My motivation notes",
"mail": "vlado#vlado1.com"
}
}
it has the correct parameters and an entry is created successfully.
What I'm doing wrong in the front-end "useMutation" and the submit function where the variables need to be passed?
I think you very much in advance for reading this and giving me some tips as I'm really stacked :(
After renaming some of the variables as #xadm mentioned I found the following:
there is a difference when I use:
const [createEntry, { error: formError, loading: formLoading }] = useMutation(APPLY);
and
const [createEntry, { error: formError, loading: formLoading }] = useMutation(APPLY, {});
e.g. passing an empty object for the options parameter actually makes things happening.

I can't add a new field to my document using MongoDB with Next.js

I want to add a new field (company) to my document. My entire API is:
import { connectToDatabase } from "../../utils/mongodb";
import { getSession } from 'next-auth/client'
export default async (req, res) => {
const { db } = await connectToDatabase();
const { name } = req.body
const session = await getSession({ req });
const a = await db
.collection("users").update({"_id": session.id},{$set: {company: { general: {name: req.body.name} }}})
};
But the document isn't updating.
Current document:
{
_id: "id"
name: "Test"
email: "test#somemmmail.com"
image: "..."
createdAt: "2020-10-29T16:25:08.908+00:00"
updatedAt: "2020-10-29T16:25:08.908+00:00"
}
But I want to add a company field:
{
_id: "id"
name: "Test"
email: "test#somemmmail.com"
image: "..."
createdAt: "2020-10-29T16:25:08.908+00:00"
updatedAt: "2020-10-29T16:25:08.908+00:00"
company: [{general: "someinfo"}]
}
I am using Next.js & MongoDB package.
The problem is that you need to convert the session.id string to a mongo ObjectId(session.id) object:
Take a look at this screenshot using mongo shell:
No results:
db.users.find({ "_id": "5fa1e18be3a0523e5ce6c50f" })
Found match:
db.users.find({ "_id": ObjectId("5fa1e18be3a0523e5ce6c50f") })
On that note, since you're only looking for one document to update, I'd recommend findOne over find.

Find subdocument object in another db

I'm trying to check each email of attendees and see if they are a registered user. If not, I will send them an email (not yet coded, will do later).
Here's the event and user schema:
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const Event = new Schema({
title: {
type: String,
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users'
},
attendees:[
{email: {
type: String,
required: true
},
name: {
type: String,
required: true
},
status: {
type: String
}}
]
});
router.post('/', auth, async (req, res) => {
const {title,
attendees
} = req.body
if (!title) {
return res.status(400).json({ msg: 'Please enter a title' });
}
try{
const newEvent = new Event({
title,
user: req.user.id,
attendees: attendees.map(x => ({
email: x.email,
name: x.name,
status: x.status,
})),
});
const attendeeExists = await User.findOne({"attendees.email":email});
if (!attendeeExists) throw Error("User doesn't exist. Send email");
The last two lines are giving me an error: email is not defined.
Not sure what I'm missing.
This works in the user routes:
const user = await User.findOne({ email });
Thanks #ambianBeing, your solution helped me get a working model.
const email = attendees.map((a) => a.email);
const attendeesFound = await User.find({email});
For checking any of the attendee's email found, .find() with $in can be used which'll return the users found with any of the email ids.
/*collect all emails to test*/
const emails = attendees.map((a) => a.email);
const attendeesFound = await User.find({ "email": { $in: emails } });
Another Mongoose syntax wihich does the same thing as above:
/*collect all emails to test*/
const emails = attendees.map((a) => a.email);
const attendeesFound = await User.find({}).where("email").in(emails);

useMutation result returning weird structure/ not returning requested fields

What I am trying to do: using useMutation to submit an array of array to mongoDB.
What happened: it did successfully save in mongoDb and in the correct format BUT the 'result.data' of useMutation is not as expected, it's too weired to be used.
This is my array of array (of course, it started as an empty array and was pushed in as user adds attraction to the days in their itinerary).
let itinerary = [{placeIds: ["ChIJafBhcoVPqEcRb7rHEy3G0L8", "ChIJBRk3gUlOqEcR9oEBV-dqK5M"]},
{placeIds: ["ChIJx8Iw5VFOqEcRXUgfWxkmAaA", "ChIJSdWeck5OqEcReSTr3YfoSuE"]},
{placeIds: ["ChIJ1WCXcFJOqEcRBR_ICa3TemU"]}]
This is the graphql mutation:
const SUBMIT_ITINERARY = gql`
mutation submitItinerary(
$dayPlans: [DayPlanInput]
){
submitItinerary(
dayPlans: $dayPlans
){
id
dayPlans{
placeIds
}
createdAt
}
}
`
This is the apollo react hook useMutation:
const [submitItinerary] = useMutation(SUBMIT_ITINERARY, {
update(result){
console.log(result.data);
},
onError(err){
console.log(err)
},
variables: {
dayPlans: itinerary
}
})
in my graphql playground I got this
So I thought in my frontend I would get something similar but instead I got this:
Why is that the case? I want to get a structure similar to what I got in graphql playground.
More information that might be helpful:
Here is my itinerary schema:
const itinerarySchema = new Schema({
city: String,
username: String,
createdAt: String,
dayPlans: [],
user: {
type: Schema.Types.ObjectId,
ref: 'users',
}
});
my type definitions:
type DayPlan {
placeIds: [String]!
}
type Itinerary {
id: ID!
dayPlans: [DayPlan]!
username: String!
createdAt: String!
}
input DayPlanInput {
placeIds: [String]
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getUsers: [User]
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
submitItinerary(dayPlans: [DayPlanInput] ): Itinerary!
}
and my submitItinerary resolver:
Mutation: {
async submitItinerary(_, {dayPlans}, context) {
const user = checkAuth(context);
//console.log(user);
if (dayPlans.length === 0){
throw new Error('Itinerary should not be empty');
}
const newItinerary = new Itinerary({
dayPlans,
user: user.id,
username: user.username,
createdAt: new Date().toISOString()
})
const submitted = await newItinerary.save()
return submitted;
}
}
The first variable of the update option in useMutation is the Apollo Cache itself not the mutation result. The mutation result can be found in the second parameter. Here you can find the API docs.
This should log the correct data:
const [submitItinerary] = useMutation(SUBMIT_ITINERARY, {
update(cache, result){
console.log(result.data);
},
onError(err){
console.log(err)
},
variables: {
dayPlans: itinerary
}
})
Also another way to access the mutation result is using the second element from the hook return value:
const [submitItinerary, { data, loading, error }] = useMutation(SUBMIT_ITINERARY)

Resources