And I'll name that method save here.
And since we'll interact with the database,
I will also already add our import
to our DB utility functionality
in the data folder and there the database file.
In the save method, we then have to differentiate
between the two cases
that we are updating an existing order
or that we're storing a brand new order.
We can differentiate by checking if we have an ID.
If this ID is truthy,
then we are updating.
Else, if it's not truthy,
we are adding a new order.
Now let's start with that new order case.
Here, I then want to create my order document
that will be inserted into the database
as a JavaScript object
using this object literal notation,
and there I'll set userData equal to this user data.
So the data that's stored in my userData property here.
And this will always be there.
This is a non-optional argument
in the constructor.
I will store my product data
by reaching out to this product data.
And now I want to save the date, but there,
since I'm adding a brand new document,
I don't have a date yet.
Therefore, I'll get a new date by simply
calling new date like this.
This creates a date object,
and if you don't pass any value to it,
it will use the current date-time snapshot.
And that can then be stored to Mongo DB
because thankfully Mongo DB knows
how to work with such JavaScript date snapshots.
Last but not least,
I'll set the status to this.status,
which as a default is pending.
So that is my order document,
which I want to store.
And therefore, now we can use db getDb,
and then reach out to, let's say, our orders collection,
which doesn't exist yet,
but which will simply be created
on the fly when we need it.
And there, I want to insert one new document
and that's my order document.
Of course, this returns a promise
and therefore we could await this
and add async in front of save.
But since I don't do anything thereafter,
I can also just return that promise,
and therefore save will return that promise,
which is yielded by insertOne,
and we can handle it in the place
where we call this save method here.
Now that's the scenario for adding a new order.
We'll work on updating later.
For the moment, let's wire this up
to our orders controller.
Here, we now want to use our order model.
So let's import it with require
by going up one level,
diving into models, and then order model.
And then we can create our order here
with new order.
And now we need to pass some data to this constructor.
For example, our cart.
That's not too difficult.
But we also need to pass some user data in there.
And for this, we, first of all, need to fetch data
about that user.
Now, if we have a look at our user model,
which we defined way earlier in this section,
then we see that in there
we have a getUserWithSameEmail method.
Now I actually want to get a user by ID though,
because the ID is something
we have stored in our session.
If we have a look at the check-auth middleware,
we do get the user ID here
from the UID field
and we store that in res.locals.uid as well.
Hence, I'll go to my user model
and add a new static method,
a static async method findById
to find a user by ID
and return some data about a user by ID.
For this here I expect to get the user ID
as a parameter value.
And then, first of all,
I'll transform it into a new Mongo DB user ID
by importing Mongo DB at the top here,
like this,
with a semi-colon.
And then here we can run new mongodb.ObjectId,
and pass user ID to that.
Now as our next step,
I, of course, want to use db.getDb,
and reach out to my users collection.
And there find one user
where _id is equal to the uid.
Now, I will actually add a second parameter value here
to find one, which allows me to apply projection.
That means I can control which kind of fields
I want to get or not get.
And I don't want to extract the password
from the database, for example,
because I don't need that.
We can make sure that it's not retrieved,
but all the other data is,
by adding password minus one here.
This second parameter value,
which you pass to findOne,
controls which kind of data you want to receive.
And if you set a field to minus one here,
it will be excluded from the data
that's being fetched.
So now we can return this here,
and actually we don't even need async therefore,
since I directly return the promise
that's yielded by findOne.
And this will then give us the user document.
Not a user model instance,
not an instance of this class,
but just the pure document.
But for our purposes here,
that's all we need.
Because I just need some raw user data.
Now, back in orders controller,
we can therefore of course also import user
by requiring models user model.
And then here, in add order,
we can convert this into an async function
and then get our user document
by using await user find by ID.
And now the ID is something
we get from res.locals, as you learned,
res.locals.uid.
That's a value we store in our check-auth middleware.
Now, of course, this might fail, as always,
so we want to wrap this with try-catch here,
like this,
and define the user document
outside of that so that we can access it
inside and outside of try.
And here in the catch case, of course,
we need next to return next here
with that error, as we did it many times before,
in the course for error handling,
when using async await.
Now we've got the user data
and therefore, for this this order,
we can pass our user document
into this order constructor.
And therefore, the data which we'll store
for this order will be all the user data
except for the password.
It would have been hashed anyways,
but still I don't want to save that.
It simply does not matter for the order.
But I will save the email, the ID,
the name, and the address.
And that is exactly what I want.
Now, all the other data is fine
and doesn't need to be set,
and therefore now, with that new order created,
we can call order.save to save it to the database.
And just as before, this is something
which we want to try-catch.
Alternatively, we could move it
into the already existing try-catch block,
but here, I'll write a separate try-catch block,
and also return or just run next error here
if we have an error,
and also await this here.
And if we succeed, if we make it past this,
then here, or alternative after try-catch,
then we need to return in the catch case though,
so that the other code doesn't execute
if we have an error.
Then here, I want to send a response
and we could redirect to /orders,
a route, which we doesn't support yet
with a get request,
but that is, of course, something we can change.
Now, to see this flow in action,
I will add a very basic orders view
so that we can register a very basic get /orders route
so that we can test this
without running into errors
when trying to redirect.
So therefore, in the views, in the customer folder,
here I'll create a new sub-folder, which I'll name orders.
And in there, I'll add a all-orders.ejs file,
and it will just grab all products,
copy that content into all-orders,
get rid of the product styles here,
rename this to 'All Your Orders'.
And in the main area,
I'll get rid of the unordered list.
And for the moment,
I'll just say 'All Your Orders'.
So that is a dummy orders template.
We'll refine it later.
Now we can add a controller action
for getting those orders,
may be at the top.
Here, a function called getOrders,
which at the moment will just render this dummy template.
We'll fetch and render the concrete orders later.
At the moment, I just render customer orders
all-orders.
That was the path to that template,
to this EJS template we just added.
Now we can export getOrders here
to make it available outside of this file.
And in the orders routes file,
here, we can now register a new route,
a get route to slash nothing,
which is /orders, as you know,
because we have this prefix in app.js.
And here for this get request,
I want to execute ordersController getOrders.
Now that was a lot of work,
but theoretically, we should now be able
to save our cart data in the database
when we click this button.
So let's reload with a logged in user
and let's click byproducts.
And I get an error,
total quantity can't be read here.
This is actually coming from the 500 EJS template though.
So it's tried to render that view
and then failed to access total quantity.
So we'll have to look into why that happened.
But in the end, it failed to process this request
and therefore it wanted to show 500 EJS
because of a missing CSRF token,
which indeed I did forget to add.
We'll still have to check why the 500 page
can't be rendered correctly,
but to solve the issue of not submitting the orders
successfully, we should go to cart.ejs,
which is where I have this form for buying products.
And there, we should add an input
of type hidden,
with a name of _csrf,
where the value now is locals.csrfToken,
as we did it many times before.
With that, if we save this,
and we reload this cart,
if I click byproducts,
now I'm redirected to the orders page.
So this looks better.
The cart is not emptied because we haven't added
any logic for this, but we're on the orders page.
And now let's see.
If I use the Mongo shell here
and I use my online shop database,
if I now find all my orders,
indeed there is some order data here.
And what you can tell is that there
I got my user data
actually including the password.
That should not happen.
Let's check on this.
But then all the other user data as well,
and then the product data with all that cart data,
which we had,
which is all the product data,
which we used before,
and of course also the total quantity
and total price,
and then our time snapshot and this status.
Now let's first fix the projection issue
with the user password.
For this, we can go back to the user model
and there password should be set to zero.
One would include it and then actually exclude
all other files except for the ID.
If you want to exclude password
and include all other files,
you don't set minus one but zero.
And in addition,
this also needs to go into a nested field here
because when you use the findOne method
of the Mongo DB Node JS driver,
this second object, which we pass to it,
is actually a configuration object,
which has a projection key,
and that then is the object
with the concrete projection configuration.
So it's this projection key,
which holds the object,
where I set password to zero.
With that, if I go back to my orders
and I actually delete the orders there
with deleteMany and then an empty object,
if we now go back to the cart
and place this order again,
and we now do get all orders again.
Now in there, we don't find
the user password anymore.
So that's now fixed.
So with that, we're storing this order data.
Of course, what we're not doing yet
is resetting the cart.
Once we place the order,
we want to clear the cart.
So to achieve this in the orders controller,
after we add the order,
right before I redirect to orders,
I actually want to go to request session
and set my cart equal to null,
to entirely reset it,
to entirely clear it from the session.
Because keep in mind, the cart
is only stored in the session.
So if we remove it from there,
we clear all the data that belongs to it.
Now, if we do that and we try placing the order again,
therefore now the cart is reset here.
And that's looking good.
That's working the way it should.
Now of course, we want to make sure
that on this orders page,
we also output all the orders
that belong to this user who is logged in.
And that's important.
Only the orders that belong to this user.