🕰️ Chapters
- Lesson Objective
- Reusing Our Avatar Upload Fields
- Reviewing Avatar Upload Logic
- Adding Poster Field Validations
- Movie Creation Cover Upload
- Movie Edit Cover Upload & Deletion
- Testing Our Cover Upload
- Moving Cover Image Upload to a Service
Transcript
-
(upbeat music) So when we add movies in via our seeder, we have these cover images being appended onto them.
-
And currently these are just coming from the Picsum service. So if we inspect this real quick, we're gonna see the URL is going directly to picsum.photos, but we haven't accounted for this cover photo at all
-
on our movie forum. So our current movie that we've added in doesn't have a cover image and it can't have a cover image. So let's follow a similar process that we took
-
whenever we added in the ability to upload an avatar, with our cover images. So if we jump into our edit profile, here is our avatar upload. We can copy that and use that as a base point
-
for our admin movie, create and edit movie form. So let's go ahead and hide our browser away. I am already inside of our profile edit page.
-
Right here is where the avatar URL determination starts. And this is just what displays the preview. If we scroll on down, then we have our hidden input that determines
-
whether or not we are attempting to remove an image. And then we have our form input that allows us to insert a new image. So we'll give all of that a copy, and then we can scroll down and find
-
our admin movies create and edit field, the page for that form. And then we'll scroll down a little bit. Let's go ahead and plop this directly above our title.
-
So just paste everything that we had copied in. I'm gonna fix the formatting here real quick. And I'm gonna Command + P and jump into our movie model, because I can't remember,
-
it looks like we called it our poster URL. Okay, we jump back into our creator edit, and now our avatar URL will change to poster URL. So we can do a find and replace all
-
of avatar URL with poster URL here, replace all there. The name of our form input will be poster rather than avatar. So we can do poster and poster there.
-
The poster URL will not be coming from our auth user, but rather the movie itself. So we can have movie dot there to determine where that loads in at.
-
And right, we need to use optional chaining there because we may or may not have a movie. If we're just trying to create, the movie's not gonna be populated.
-
See, we had added edit form to our actual form ID to allow us to easily clear that out. And right up here, we have another instance
-
where we need to do movie and right here as well. So movie, and then we'll add our ID of edit form.
-
Maybe we'll switch that to movie form for our form itself. So movie form there too. Cool, so if we jump back into our browser real quick and check out what we have,
-
we have a basic input because we currently do not have a poster for the movie that we're attempting to create. But if we jump into say movies, let's jump into one that we seeded.
-
So let's edit this one. We do have a cover image here and we can clear it out. Awesome, so it looks like everything that we have within EdgeJS is working so far. I'm gonna go ahead and jump into a movie
-
that's already created here, hide this back away. And relatively similar to how we are saving our avatar,
-
we need to take the same approach for our movie store and update handlers. So if we jump into our profiles controller, let's remind ourselves what we're doing here.
-
We have the avatar and avatar URL coming from our validator. Then we're doing an if check to see whether or not we're attempting to upload a new avatar. If we are, we're moving that into our storage
-
underneath the avatars location and persisting that onto the user's avatar URL. If we don't have an avatar and there isn't an avatar URL,
-
but there is one on the currently loaded user, then we're going to remove and delete that avatar image. And then this all gets saved right down here
-
where we're saving the user. So the first thing that we're gonna wanna do is jump down to our validators and our movie validator. We can just go ahead and add this above the title as well. So we're gonna have a poster field,
-
which will be the actual image of Vine type file. And this will be optional. And then we're also going to have a poster URL
-
type Vine string optional. For the file, we're going to want to add some extension. So ext names restrictions.
-
Remember, these are the file extensions that we want to allow the user to be able to upload. We do png, jpeg, jpeg. And I have a gif image.
-
I don't have many images on my machine, so I'm gonna allow gifs here as well. And then we can also specify a file size limitation of say five megabytes.
-
Cool, so that should be our validator taken care of. If we scroll back up to not that movie's controller, but our admin movie's controller now, jump into here and scroll on down.
-
Currently, we are taking in all of the data from our validator, merging that into the movie itself and using that to create. And if we scroll on down to our edit, I believe we're doing relatively similar.
-
So yeah, we're just taking in the movie and it's validated data and merging it together. So within our store here, really all that we care about for uploading
-
is the poster itself. We don't care about the poster URL at this point because we don't actually afford the user the ability to manually specify a poster URL themselves,
-
but rather that's a property that we use after the actual upload is done. So we'll take our poster out of the validated data and we can spread everything else into data.
-
And then we can do an if check. So if poster await poster dot, remember this is a multi-part file, so it has added abilities on it.
-
And we're going to want to move this file from its temporary location to a more permanent location on our file system. And for that location,
-
we can use the app module once again to make a path, just like we are with our avatars. We'll keep this inside of a directory called storage and we can put it inside of a subfolder
-
called posters. Now we did not do this for our avatars, but it is good practice to rename the uploaded images to prevent collisions. So we'll go ahead and do that here.
-
So we can provide in a custom name and we can use the kuid helper from AdonisJS and call that, which is going to generate out
-
a collision resistant ID for us. So we can use that there as our file name. And then we just need to grab the extension,
-
which we can do via poster dot ext name for extension name. Once we have that poster moved where we need it,
-
we can go ahead and add it onto our data dot poster URL as slash posters slash.
-
And we are going to need to get a reference to this name. So instead of generating it directly inside of the options here, let's go ahead and save it to a variable.
-
So const file name equals, and then we can provide that into the name there. Give this a couple of line breaks. And now we can plop the file name
-
into our poster URL string. Give that a save for formatting. And when all said and done, this will get merged in with the created movie right down here,
-
since we're plopping and mutating the data directly. If they are not uploading a poster, then poster URL will just be undefined since we're not defining it anywhere on our form.
-
If you need to explicitly make sure that a user is not trying to force a poster URL in there, you can always do an else here and clear out that data poster URL.
-
For us, I don't think we need to worry about that. Cool, so that should take care of our creation step. Our edit step is going to be relatively similar. So we can go ahead and give this block here a copy,
-
but we also need to worry about instances where the user may be trying to remove the image as well. So we'll do this right here, just like we did before.
-
I think we want to spread out our poster and then we can leave poster URL in data so that we can mutate it. So we'll just spread everything else directly into data there.
-
So we have our if poster taken care of. Now we want to do our else if. So else if not data.posterURL
-
and our movie.posterURL has a value. So if the movie did have a poster URL, but we no longer have one defined in our form,
-
that's where we're going to want to await and use unlink from Node.js. And I'm going to use the one specifically from Promises there.
-
Provide in our app.makePathToStorage and then merge into the path at movie.posterURL.
-
Then to clear out our poster URL, we can do data.posterURL equals. And we have this not nullable in the database defaulting to an empty string.
-
So we'll just set this to an empty string to clear it out. Give that a save. Then we jump back into our browser, jump into one of our manually added movies,
-
browse, select a movie, open it up, scroll on down. Let's try to update our movie. And, oh right, we forgot to make this a multi-part form.
-
So we need to scroll on down to our creator edit. On the form itself, let's add ink type and select multi-part form data there. Now we should be good to go ahead
-
and jump back into our browser, browse, select our photo, open it up, scroll on down, update our movie. And it seems to have worked. Now we haven't added the ability
-
to actually view this image yet. So if we go into edit, we'll see that it at least tried to render it out, but it's not going to be able to find it. But the fact that it's trying
-
means that it saved successfully onto our movie itself. Furthermore, if we had our browser back away, scroll on down to our storage directory. We see that we now have a posters directory
-
with an actual randomly named file inside of it that shows our picture. So everything does seem to work A-okay there with the save process.
-
Let's go ahead and try to remove the image now. So if we hit our X, scroll on down, update. Now, whenever we go into edit, we should see that it no longer tries to render out that picture,
-
meaning that it's now detached from the movie itself. And if we had a browser away, our poster directory is now empty. So the file itself is gone as well.
-
So that seems to be working A-okay. If you wanted to, we could also move this portion here into a service. So if we scroll on up to our services,
-
I'm just going to go ahead and select from here to there, give that a copy, jump into our movie service, and we can do something like static async store poster,
-
paste that in, and let's see, we're going to need the poster image itself. So poster, and this will be of type multi-part file. So I'm going to scroll down to that
-
and import it from the Adonis Jazz Core body parser. Now we also have qubit that we need to import. So I'm gonna hit command dot and let's just add all missing imports.
-
That'll take care of our app import there as well. Now you'll see data is unhappy because it doesn't exist in this context, but we don't really need it to.
-
Instead, we can just return that value back like so. Then let's jump back to our movie controller. We can get rid of everything above our data poster set
-
and instead just call await movie service dot store poster and pass in the poster image,
-
which will return back our final poster URL. Give that line a copy, and now we can scroll on up to our store and do the exact same thing here.
-
So we can paste that in. And now we've removed that duplication and we can scroll up to the top of the file and remove the unused import there too.
Uploading Movie Cover Images in our Create or Edit Form
We'll learn how to upload movie cover images when either creating or editing a movie via our create or edit form.
Join the Discussion 3 comments
-
When testing in Postman, I encounter a "slug uniqueness error" on the third consecutive form submission without changing the input. The error occurs in the store method, which validates the request, processes the poster file, and creates a movie record. The database throws a duplicate key error because the slug already exists, violating the movies_slug_unique constraint. This likely happens because the slugify method, provided in the course, fails to generate a unique slug for repeated sub
1-
Responding to codthing
The issue lies in the fact that only English letters are supported, so we should first ensure that the title is in English, and then verify its uniqueness. It is recommended to use the transliteration library.
1-
Responding to codthing
Ah, I'm happy to hear you were able to track down the root cause! Sorry there wasn't a warning in-video about that, codthing! Locales on the slugify didn't even occur to me.
1
-
-