In this chapter our goal is to create video thumbnails which we can actually upload. So far we've only had thumbnails which were generated using the playback ID coming from a Mux webhook. We're going to use upload thing as our upload service And this is the UI component which we are going to develop. So this component will be right here above the category and once clicked it will give the user options to either upload a thumbnail or to restore the thumbnail to its initial initial state which was the max webhook one. And we're also then going to refactor our thumbnail fields in the schema so that we can have a proper Upload Thing cleanup.
Let's start by integrating Upload Thing. So head to UploadThing.com and go ahead and create an account. Once your account has been created, let's create our first application here. I'm going to call this application new tube like this and I'm going to select the free tier. I'm not going to fill any of these options, I will just click create app.
There we go. Now let's go ahead and let's click on API keys here. So it looks like we only have one key, upload thing token. So I'm going to go ahead and click copy here. I'm then going to go ahead inside of my dot environment dot local.
And I will add it here. As always, don't share this with anyone. Now, let's go ahead and let's click on the docs here so we can see the proper way to add this to our project. I'm going to select Next.js app router, and I'm going to follow the instructions for bun. So we have to add upload thing and upload thing react.
I'm going to go ahead and add these two and then I'm going to show you the versions. So 7.4.4 for upload thing and 7.1.5 for UploadThing slash React. We already added the environment variable, so that's fine. Now let's go ahead and let's set up a file router. So we're going to go ahead and create inside of the API folder, upload thing and then core.ts.
Let's go ahead inside of source app folder, API, new folder, upload thing and then inside core.ts and paste the file inside. Inside of here you will have a very basic mock authentication method as well as the basic image uploader router here. We will later modify this entire thing, but we can leave it like this for now, and you can ignore the type error here. After you've established the core.ts file, Let's go ahead and create a route.ts. So I'm going to go inside of upload thing and add a route.ts and add it here inside.
Inside of here we are importing our file router from core and we are assigning it right here. Let's save this file and now let's go ahead and let's create the upload thing components. So I'm going to go ahead and copy this and you can choose where you want to put this. Let's put it inside of source utils upload thing simply so we stay true to the documentation. You can of course move it to your place of preference later.
Let's go ahead and add upload thing.ts instead of our lib folder and let's put it here. We now have to modify this to use an at sign because that's the alias that we use. There we go. We now have upload button and upload drop zone which has strict types to our file router here. So later when we define thumbnail uploader we will know exactly what we have to pass from the front end here.
And I think that that's it. All we have to do now is add this wrapper around our Tailwind config. And I can see that they have actually added Tailwind version 4 here, which is interesting because Tailwind version 4 just came out. They are very fast. So in our case, we are using still Tailwind 3.
I'm not sure if, can we see that anywhere? Tailwind, yes, Tailwind CSS 3. So we have the config file here. So this is what we have to do. We have to go inside of Tailwind.config.ts.
We have to add with upload thing, and then we have to export default with upload thing and wrap the entire configuration into that. So go to the end and end the wrap here and this should still satisfy the config and yes we still have this require error but again it will not interfere with our app at all. What we have to do now is we have to mount a button and upload. Well we can already try that but we can stop here. I mean this is everything we need for now.
If you want to continue reading, but what I'm going to do is I'm going to go back and set up my dashboard so that I can actually see my files being uploaded here. So what we're going to do now is we're going to go ahead and start working on this component right here. So let's go ahead inside of our form section here. And what we have to do now is we have to create those components which I was talking about right here to do add a thumbnail field here. So that's going to be a form field in itself.
And now let's go ahead and give the form field a name of thumbnail URL. Let's give it control of form dot control. And let's go ahead and give it render. And let me just move render here. So I'm going to destructure.
Actually, we don't need any field here because, well, technically, yeah, this is a form field, but we're not going to use it exactly the same way we use our previous ones, in a sense that it's not going to need any kind of form control because all that this is going to do is it's going to open up a model and display the current items. So let's add form control here. Let's add a div, class name padding 0.5, border, border dashed, border neutral 400, relative, height of 84 pixels, width of 153 pixels, and group like this. So I think I'm missing a little space here maybe. There we go.
Make sure you put space between the width and the height. And then inside of here, you're going to use an image from next image. So just make sure you have added an import for that. And we're going to give this fill. Alt will be thumbnail.
And we're going to have a source, which will either be video.thumbnail URL or slash placeholder SVG. And let's also give this a class name of object hover. There we go. So now you can see my current thumbnail right here. One thing I want to do before we move forward is standardize this.
We are using it in three files in four places. So what I'm gonna do is I'm gonna go inside of videos and I'm going to create types.ts here. Actually not types, I'm gonna do constants.ts in the videos and I'm gonna export const and I'm going to export const thumbnail fallback. And I'm going to add slash placeholder SVG here. And then I'm going to search for placeholder SVG again, starting with our form section.
I'm going to go ahead and fall back thumbnail for fallback here, which was auto imported from modules videos here. I'm going to go ahead and continue searching. Next one is inside of videos, UI components, video player. So let's go ahead and use the thumbnail. All back here from a dot dot slash dot dot slash constants.
And the last one is in the video thumbnail component again, instead of the videos module UI components video thumbnail. So two places here where we are going to modify to use thumbnail fallback from dot dot slash dot dot slash constants. And if you want to, you can also, you know, be consistent whether you want to use double pipe or double question marks. So let me just see all the places where I use it. Inside of form section I'm also going to replace it here.
I think there's no need for double question marks here. There we go. Now I'm consistent in all of the places where I'm using this. Great. Let's focus back on our form section here.
Nothing should change as of now. This should still work as expected. But what we're going to do now is just below the image, we're gonna add a dropdown menu. I believe we already have the dropdown menu because we have used it to generate our delete button. Inside of here let's add the drop down menu trigger and give it an as child property.
Let's add a button component which will have more vertical icon. We already have all of these imported. It seems let's give this a size four and text white. Actually, size four is not required explicitly. Let's go ahead and give the button a type of button.
This is very important. A size of icon. And let's go ahead and give it a class name of BackgroundBlack50. On hover, BackgroundBlack50 as well. And let's also go ahead and do the following.
Confirm that inside of this div you've added a relative class so that then we can safely give this button an absolute class and position it like this. And now you should see little three dots here It's kind of hard to see how this looks like because the background is actually black as well But it looks... Well, it looks okay Not sure where I was going with that. Okay, now let's go ahead and do the following. Let's make it fully rounded and let's give it an opacity 100 on mobile.
But when we hit the medium breakpoint, by default, this button will not be visible. Only when we do a hover, will it be visible again. And the group will be this div as well. So this div needs to have group and relative in order for this button to work. I mean you don't have to have all these styles if you don't want it.
And let's go ahead and give this a duration of 300 and a size of 7. There we go. So now only when I hover, does it actually show the little more vertical dots here. Great. So now let's go ahead and let's add the drop-down menu content.
Give it an align of start and the side of right, open up the drop-down menu item and in here we're just going to have change with image plus icon. Give this a class name of size 4 and mr of 1. Make sure you have imported the image plus icon from Lucid React. So now when you click here you should have the option to change a thumbnail. And this will actually open the upload model, which we are going to create.
So what I want to do now is I want to copy this drop down menu item two more times. So we should have three options for the thumbnail. The second one will be AI generated. And the last one will be restore. So we should have these three options.
And now let's add respective icons for each of them. The AI generated will use the good old sparkles icon. You are probably used to seeing this icon everywhere by now when it comes to AI related things. And the last one will be the rotate CCW icon from Lucid React, of course. There we go, change AI generated and restore.
So now today, I mean, in this chapter, we're going to implement the change and the restore one, and we're gonna leave the AI generation for later because we will do all AI generation in one chapter which will also be for description and for the title. So let's go ahead and focus on the change one. In order to do that we first have to implement the Thumbnail Upload Model. So let's go ahead inside of Modules, Studio, Components, and create the Thumbnail Upload Model.tsx. Like this.
Thumbnail Upload Model. And let me just... I have so many things open. I will just close all of them and go inside of thumbnail model open. Let's go ahead and let's import responsive model from components responsive model.
And let me just copy the interface so you don't have to see me write it out. Basically, we need a video id for the thumbnail for the video which thumbnail we are going to change and then your normal open and an open change states. Let's export the thumbnail upload model And then let's go ahead and let's destructure the props. So video ID open and on open change. And inside of here, what we're gonna do is we're gonna open a responsive model.
I'm gonna add a paragraph, say hello. And in here, we are gonna give this a title of upload a thumbnail and open prop as well as an open change prop. Like this. And now let's go ahead and actually use the thumbnail upload model. So let's go back inside of our form section here.
Let's go to the beginning here and let's encapsulate the entire thing inside of a fragment like this and you can indent the whole thing inside and then we're going to render the thumbnail upload model which is going to come from slash components thumbnail upload model. So now let's go ahead and let's prepare some states for this. I'm going to add that here. So we're going to have thumbnail model open and set thumbnail model open to use state and false by default. And how about I move...
Okay, now this can stay here. It's fine. Let's go ahead and use those now. So we scroll here to where we rendered this. We give it an open of thumbnail model open and on open change will be set thumbnail model open.
And video ID will be our video ID. Like this. And then let's go ahead and find our drop down here, this one with the image plus icon and change. And let's give it an on click of set thumbnail mode open to true. There we go.
So now when I click on change, I have an upload thumbnail model. So what we're going to do now is we're going to go instead of thumbnail upload model and we are going to revisit our upload thing here. So our upload thing lib and in here we have the upload drop zone. So let's go ahead and render it inside. Upload drop zone, Not from the package itself, from our abstraction over it in the lib like this.
It's gonna be a self-closing tag and it needs an endpoint and it's gonna give you the option for the only endpoint it knows, which is defined inside of our API upload thing core right here. So similarly to TRPC, they have very good end-to-end type safety. That makes sense because the author of upload thing is also the author of the t3 stack which uses the TRPC. Great! So now there we go.
We have a nice little upload a thumbnail drop zone here. Now obviously if you go ahead and try and upload something here, so I just have prepared some random file, feel free to use any image for this, it really doesn't matter. And if you go ahead and try now, let's see what's going on. Upload completed for user ID, fake ID, and we have the file URL. So this should mean that now in here if I refresh let's see there we go inside of my files I have a thumbnail dot png right here excellent So what we have to do now is we have to go inside of core.ts because remember we have this video ID but we have no idea for which video are we actually uploading a thumbnail.
So this is what we are going to do now. We're going to go ahead and remove this fake out from here and we can remove all of these comments here. Let's go ahead and do this. No need for any of this. So we are simplifying this by a lot.
And let's change this to not be an image uploader but instead a thumbnail uploader. We can leave the same rules for the image 4 megabytes maximum file of one that's fine and now similarly to trpc procedures we can use zod make sure you just import it we can use zod to add any input here so object required video id there we go And now inside of here we are not going to need to destructure the request but instead we can destructure the input like this. And what we're going to do is we're also going to do a wait out, but our out here will simply come from Clerk Next.js server like this. And this will just have user ID here. If we don't have user ID, we can throw new upload thing error unauthorized.
And then in here, we can return back the user ID, which we just got from the out function. And also we can just spread the input like this and then in the on upload complete we can go ahead and do the following we can do await database which you can import from s slash db dot update videos which you can import from the database schema, and simply set the new thumbnail URL to be file.url, where equals videos ID, metadata video ID, like this. And import equals from drizzle ORM. One thing that's missing here, and which we can already fix is by importing and from a drizzle ORM. And let's ensure our usual two queries when it comes to updating the record.
So first what needs to match is this metadata video ID. The second is videos user ID, metadata user ID. But I can already see this is not going to work. Let's go ahead and be more specific here. This is clerk user ID.
Let's go ahead and check it like this. We're going to pass the clerk user ID here. This is what we can do. We can do const DatabaseUser, or just call it user, and do a wait, database, select. My apologies, just select from users.
Make sure you have users imported from schema here, where equals users ID, my apologies, clerk ID is equal to clerk user ID. If we can't find this user in our database, it means that there's something wrong with our webhook synchronization. If the clerk user exists but not the user in our database, something's wrong. We can't really do much with this user at that point. So we're going to stop that here and we're not going to bring back the clerk user ID.
We'll just bring back the entire user here. How about we do that instead? Great! And now we have user here so we can use metadata.user.id. There we go.
You can return whatever you want. I think we can return back the file. Can we? Looks like we cannot. All right.
So I'm just going to return back uploaded by metadata user.id. Right. So we have to check if user ID is the user ID from the database, not just the clerk user ID. That's why we need to do this first and only then allow someone to change the thumbnail URL. There we go.
So now let's change this to be thumbnail uploader and then we need input here and passing the video ID. There we go. And then we can decide what to do on upload complete, on client upload complete, right? And what we are going to do here is the following. We will get the utils from trpc, from the client, make sure you don't accidentally import from the server trpc.useutils and then we're going to develop const on upload complete We're going to close the model by calling on open change and set it manually to false.
Let me just fix this. And then utils studio get1.invalidate for the ID video ID like this. And let's go ahead and pass that here. If you want to, you can also re-invalidate Studio Get Many in order to immediately, you know, call that in order to immediately see the new thumbnail in the list of your videos there. I mean eventually it will get re-invalidated by itself but if you wanted to immediately see it you can validate this as well.
Let's go ahead and try it out now. So I'm going to go ahead and select Change here, and I will upload my demo thumbnail. I will click Upload, and after this, it should close, and it looks like we have an error and that is because we are using a new image source and we did not add it to our host names. So let's go ahead and confirm that the host name is utfs.io. If yours is something else, okay, just you know, you have to read what it is here.
And then you have to do the same thing we had to do when we used a mux image. So go inside of next.config.ts and you can copy this object for mux and simply replace with utfs.io. This will now restart the entire server, so you can hit refresh and you will wait for some seconds, but then It should show you the new thumbnail. Hopefully, there we go. Demo thumbnail is showing up right here.
Perfect. So we have that finished. What I want to do now is I want to implement the restore method. And then we're going to have to talk about something. And that is file cleanup, right?
Especially after restoring or after uploading a new file. How do we get rid of the old ones and make sure they don't unnecessarily populate our storage. In order to do that, we're going to have to refactor some thumbnail fields inside of our schema. So let's go ahead and do the restore method. So the restore method will be purely done inside of the videos module, specifically inside of its procedures right here.
So what we're going to do is we're going to add restore thumbnail procedure, which will be a protected procedure. I think that we can copy literally, you know, exactly as it was below. This is going to be asynchronous and this will be an arrow function like this. Inside of here, we can destructure the context and the input. As always, we can get the database user here using context.
And then we are just going to go ahead and reset the thumbnail URL. And we can do that by first getting the video. So let's do existing video. And let's do await database select from videos, where we're going to use and, and then two equals inside. The first one will be if videos.id matches the input ID.
The second one, videos user ID, matches the user ID which will be structured above. If we cannot find the existing video, we're going to throw new TRPC error with code not found. Otherwise, let's go ahead and let's create a new thumbnail URL, which is very simply going to be the same thing that we did in our videos webhook. So you can go ahead and find video asset ready here, and you can just copy this. There we go.
So just go ahead and add this here. And let's also do this. How about we also check if existing video has no max playback ID. In that case we can also throw a new TRPC error because there's nothing for us to fall back on. So we will just throw a bad request here and then we can go ahead and use this inside of here.
There we go. And then let's go ahead and do updated video here to be await database update videos set the new thumbnail URL here where let's go ahead and do double equalization just in case so videos id matches the input id and our videos user ID matches our user ID and returning and then we can return the updated video. There we go. So that is our restore thumbnail procedure. We can now go back inside of the form section here and we can now add it here.
So I'm gonna go ahead and go find one of my removed procedures for example let me just copy it here I will call this one restore thumbnail. PRPC videos restore thumbnail. So what we're going to do here is we can invalidate many, we can also invalidate one We can also invalidate one by passing in the video ID. ID, video ID, like this. There we go.
And in here we're going to say thumbnail restored and we can remove the router push here. This can stay as something went wrong. And now all we have to do is use this. So let's go ahead and go find our drop downs here. There we go.
So restore, let's give this an unclick. Mutate and pass in ID, video ID. There we go. So now if I go ahead and click restore, it restores the thumbnail back to its original position, right, Because we are using the exact same thumbnail URL string as we did in the webhook. There we go, perfect.
So we did the following, let's go ahead and mark it. We integrated the upload thing, let me increase the opacity, We integrated upload thing, we added upload functionality, we added thumbnail restore functionality, and now we have to take care of a proper upload thing cleanup. Because at the moment, you can see that now I have even more you know even less reason to hold these two files they should have been cleaned up by now. So you can clean up files by their keys right Now if I click copy file key you can see that let's go ahead and test this out. So it ends with mr and if I go ahead and open this url here and let me just enable this developer mode this part you can't see But this last part in the URL seems to be equivalent to the file key, right?
MR. But in case this changes in the future, or the URL structure changes in the future, I don't want to rely on the URL. So what we are going to do is the following. We're going to go inside of our schema here. And we're going to go ahead and alongside the thumbnail URL, also add a thumbnail key.
And make that a text. And we can even do the same thing for the preview here. So preview key. You're going to see why I'm doing this in a second. So yes, technically what we could have done is created another entity called file, which would have its URL and key fields.
But for the sake of simplicity, I kind of feel this is okay. I feel like it would be premature optimization to work with a whole another relation in my database. And it would just unnecessarily complicate things. You can of course decide for yourself, but I will choose this option. So I will add the key here and the key here.
I will then do Bonnex Drizzle kit push. Let's just confirm that I have everything I need here. There we go. Change is applied. And now things are going to be a little bit different.
Starting with my uploader here, the thumbnail uploader. So what we're gonna do is we're going to store alongside the thumbnail URL, thumbnail key like this. But that's not all that we're gonna do. Now that we know that we're going to have keys, we can leverage server-side API from UploadThing. So let's go ahead and do that.
I'm going to go ahead and use from UploadThing server, UT API. And then I'm gonna go inside of my middleware here. I'm not exactly sure if this is the correct place to do this, but I feel like we can. I mean, okay, we can also do it on the on upload complete. I would rather do it in the middleware because I don't even want the file to be uploaded if the old one is not already deleted.
So this is what we are going to do. Since we have the video ID in the input, what we can do after we fetch the user is the following. We can check if we have that video which we are trying to update. So, await database, select from videos. I believe we already have the videos schema and let's pass in two equals here.
One will be videos ID and inside of here we're going to have input video ID. The other one will be videos user ID and this will be our user.id because we do get the new user from the database here. And we can just select the thumbnail key. Like this. Is this how I do that?
I forgot how you select things in Drizzle. Just a second. You use this key. My apologies. So, videos thumbnail key.
That's how you select a single item. So we only need this, right? I don't need any more information. So what I'm gonna do is first check if there is no existing video. In that case, I can immediately break this whole method because we won't have anything to upload later on.
So this will be bad request, actually not found. Let's call it like that. And then what I'm going to do is I'm going to check if I have existing video thumbnail key. I'm going to define UTAPI as new UTAPI. And then I'm simply going to await UTAPI.deleteFiles.
And I'm going to pass in the existing video thumbnail key. And only after this is finished, I'm gonna go ahead and clean up my video. So update videos, set thumbnail key will be set to null and thumbnail URL will be set to null as well. And then let's add our equals here. We can copy it from here.
There we go. So now we have proper cleanup before we do on upload complete. Right? So inside of here we ensure that we first have to delete the existing files and reset our thumbnail key and thumbnail URL back to null. So let's go ahead and try out if that is working.
I'm not going to remove everything from my database here. I mean, from my upload thing database. Let me go ahead and delete this now. And I will refresh this as well. And I'm going to start by adding a new file like this.
And I will click upload. I should now have this new file uploaded in a second. There we go. So we didn't break anything with our new middleware. And if I go instead of upload things now, I should have the new thumbnail.
So now I'm gonna find another image. So go ahead and find some new image with a different name so it's easier to demonstrate. So confirm that you have the thumbnail in here And now go ahead and change it to another thumbnail. So I just have another thumbnail here and let's see what's going to happen. So this should still work just fine.
Great. I now have a new thumbnail and if I refresh here, There we go. You can see the old file was deleted and I only have the new file inside of here. So our cleanup function works well. Great!
But what doesn't work is my restore method here. So how about we go ahead and do that? Let's go inside of procedures here and we're gonna have to do the same thing. So we have existing video here How about we do the following? I'm not sure if we should do that before we throw this error or not.
Well, yeah, I think we can do that before we throw this error because this is a logical block when it comes to assigning the thumbnail URL from the playback ID. So I'm going to leave that in this, but I'm going to start the cleanup here. So how about we do the following. If existing video dot thumbnail key. In that case, let's go ahead and do the same thing with it in core.
Basically, This is what we need to do. Let's go ahead and get the UT API from upload thing server like this. Delete the files using the thumbnail URL and then update this in the database. So let's just change this. This is input.id and this is pure user ID here.
There we go. So just await these two elements and then continue with further updates and assigning of the new URL. So now obviously this does bring one question though. Yeah I'm thinking do we need to maybe upload this to upload thing as well? Because we can do it using UTAPI.
UTAPI has something called upload files from URL. So technically we could just pass this and then have consistent file storage across our project. Let's see, we're gonna see. I think that right now everything should still work fine. So right now I have only one file in here And if I go ahead and click restore, this should revert everything to my original state.
And if I refresh here, no problem at all, right? No files at all. That was what I was searching for. Great, so we have proper cleanup. So I'm still discussing, you know, should I do this or not?
Because now we have a state where we added the thumbnail URL but we didn't add the thumbnail key. But then at the same time we don't really care, right? So I think I'm going to leave it like this for simplicity sake. So we're gonna know that if we are missing a thumbnail key, that means that's not something we can remove in the first place, right? So if you want to, you can decide for yourself.
Do you want to use the imagemux.com or do you want to upload this URL to one unified upload service like this. But I think we managed to do everything we had to do, which is refactor the thumbnail fields in the schema and have proper upload thing clean up. But now it doesn't really make sense that we changed our database schema preview key, right? We never use this. I prepared this so that when we are inside of our webhook in the videos when we get the thumbnail URL and the preview URL so that we can actually upload them.
Maybe we should do that. I think I would be more satisfied if all of my files were in one place and not just randomly generated like this. Let's do that. I think that's a better course of action. We're gonna go do that and then we're gonna wrap up this chapter.
So for the videos webhook here let's go ahead and let's import UT API here like this and then let's go inside of our video asset ready and then we're going to rename this to temporary thumbnail URL and temporary preview URL. And then inside of here We're going to define UTAPI as new UTAPI. And we will be able to do await UTAPI upload files from URL like this. And pass in temporary thumbnail URL and the temporary preview URL. And inside of here we're going to have the uploaded thumbnail and uploaded preview like this.
So what we are gonna be able to do now is first of all, check if any of these are missing. So if we don't have uploaded thumbnail.data Or if we don't have uploaded preview.data, let's return new response here, failed to upload thumbnail or preview, and return a status of 500. You know, or actually I'm not sure if we really have to break the entire webhook just because we cannot generate a thumbnail. I don't think that should be a webhook breaking event because users will be able to upload the thumbnail anyway. Right?
So, yeah. Let's see. Let's leave it like this for now. I'll see what I'm going to do with that. And now what we can do here, let me just move the duration alongside these generations here.
So then here what I'm going to do is I'm going to from uploaded thumbnail.data I'm going to the structure key, thumbnail key and the URL, thumbnail URL. And from here, I'm going to do uploaded preview.data. I'm going to have preview key, and I'm going to have preview URL here. And then we're going to use those. So, oh, we are already using them.
We're just not using the keys, I think. So yes, let's add thumbnail key here and let's add preview key here. So now we can always store them inside of our file storage and remove them from our file storage as well. So this should now work just fine but we also have to do the same thing for our restore method. So let's go ahead inside of our procedures for video server here.
And instead of calling this thumbnail URL, let's call this temporary thumbnail URL. And then we can break out UT API here. We already have it. Perfect. And we can simply get the data here.
UT API upload files from URL, temporary thumbnail URL like this. And I believe that this will just return. Let's await it, of course, first. This should return back just data. There we go.
Now I'm just not sure, should I throw an error here or not? Well, yeah, we can throw an error here. How about we call this uploaded thumbnail? Let's store this like this. In case I have no uploaded thumbnail data, I will throw new PRPC error here.
Code internal server error like this. And let's go ahead and finally, the structure, the key, thumbnail key, and URL, thumbnail URL, from uploaded thumbnail.data. So we should have both of these now. There we go. So now we are only using upload thing to store our files.
We should never really load this on our front end in the end. We are simply using Mux as a generation tool and then uploading it. This is really good because we don't depend on Mux changing their API, right? Imagine they change it without warning you and all of a sudden all of your thumbnails have been broken, right? I think this is a much better way of doing this.
Let's try all of this now. So I'm gonna go ahead and do the following. I'm first gonna go inside of upload thing and make sure that you have no files inside of here. I'm then going to go ahead and run my Drizzle Kit Studio here and I'm going to remove all of my videos here. So let's just go ahead and do that.
So I'm going to remove all of my videos And I will now go inside of content here. And I'm going to simply create a video. Let's select files. Let's upload a demo video here. Let's wait a few seconds.
And I think we can already start refreshing here in the upload thing and there we go. I have thumbnail.jpg and it says this is a server-side upload, basically meaning it came from our webhook. And if I click here, it is that very thumbnail, which usually we just used the pure link to image.mox.com from. But now we can just use upload thing, which would also mean that we will never actually encounter this inside of Next.js image, so you could remove it. Yeah, let's actually remove it.
I think we need it. I'm pretty confident we don't need it. Just make sure that you removed all videos from your database so none of them have... My apologies, I punched the microphone. So make sure that none of your video entities have the old image.mux URL now.
So I'm just gonna go ahead and refresh this and then we're just gonna go ahead over our entire flow again. So I now have this here and I also have animated.gif. Yes, we forgot about that. I just, I read it as GIF. I'm so sorry.
It's GIF. So we also have this. And now if I go ahead inside of my content, when I hover, it works. There we go. So both the GIF and the original thumbnail are now stored in the upload thing.
And if we ever want to, we can properly remove the preview, right? Because we have the preview key. Nowhere in our app are we actually ever going to remove that because we will never allow the user to upload their own preview but if we ever want to do that we will be able to have to do that because we separately stored the preview key we don't rely on the URL at all. Great! Now let's go ahead and try doing the following.
Let's change this image to my blue thumbnail here and let's see what will happen. So the image right now should change. There we go. And let's refresh this one. There we go.
The old one was deleted and only the new one is available. Now let's go ahead and try uploading another one and see if it will still work. So We have to check all of those places, right? The web hook, we have to check the image uploader and we have to check the restore in the end. So now we have a red one, which means that when I refresh here, there we go.
Only this one is available. So we have proper cleanup. And one last thing to try, which is the restore method. So we are now restoring everything from scratch. Thumbnail restored.
There we go. Let's refresh. And we have the original thumbnail.jpg back. Perfect! So we have very powerful cleanup, we are not wasting any storage, and we are fully utilizing Upload Thing in our project.
Amazing, amazing job! Job.