Now that we finished our following system, what I want to do is I want to create a blocking system. So in order to do that, the first thing we have to do is add it inside of our Prisma schema. So let's go inside of here and just as we created this model follow, we are now going to create a model block which is going to be very very similar to this one. So let's go ahead and write model block let's give it an id which is a string it's going to be our primary id with the default value of uuid and then it's going to have just like above we had the follower ID and following ID. Here, we're going to have the blocker ID, which is a string, and we're going to have the blocked ID, which is a string.
And then we have to create two relations. Relation for the blocker and we have to create a relation for the blocked user. So first let's do the creation for the blocker. So that is a user with a relation which we are going to name blocking. Fields for this relation are gonna work with blocker ID.
Blocker ID, so this field right here. And it's going to reference, so references an array to ID. Let me zoom out so you can see this is all supposed to be in one line when you're writing this. So references ID which refers to the user ID, right? So references ID and after that we're gonna add on delete to be cascade like that.
And while we are here, oh I misspelled this, so this should be fields like that. And you should be having an error here because we don't have an equal relation for the user. So we're going to come back to that for now. Let's just create the exact same thing but for blocked. So blocked is also a user relation name blocked by fields blocked id references id and on delete cascade.
So keep in mind that all of this is supposed to be in one line as I've zoomed out right here. And now that we have this we have to go back inside of our user model and we have to create the equivalent relations. So just make sure that in the blocker relation, the name of the relation is blocking and the fields is blocker id. And in the blocked relation name is blocked by and fields is blocked id. And now go back inside of your user and just as we created the following and followed by let's create blocking to be the model block and the relation name for that is going to be blocking and then create blocked by which is also block but relation name here is blocked by so very similar to what we had here for the following And now what we have to do is we have to go back to the block model here and create the indexes.
So let's go ahead and create an index for blocker ID. And let's create an index for the blocked ID. And then let's also add a unique constraint on the blocker ID and the blocked ID. So this will also create an index for us for faster querying. So just make sure that you have this exact model block.
Again, confirm that the blocker ID is used in the blocker relation, which we named blocking, and ensure that the blocked ID is used for the blocked relation, which we call blocked by and use it to fill blocked ID. Great, now that you've done this, let's go ahead inside of our terminal, shut down your app, this is important, otherwise you're gonna have some errors and do npx prisma generate so we add those to our local environment and then npx prisma db push so we add them to our local database and only after you've done those two go ahead and confirm by doing npx prisma studio so this should open it on localhost 5555 so let's go inside of our browser here and let me just try that out and now when I click on this plus here there we go I have a new model block alongside my follow and my user. So we successfully added that. Great! So you don't have to start the application just yet because what we're going to do now is we're going to develop our block service.
So let's go inside of our lib folder and create a new file block service.ts. Let's go ahead and let's import the database. Let's go ahead and let's import getself and what I'm gonna do is just replace this imports to use at slash lib like this. You don't have to do this. And now let's do export const isBlockedByUser id is a string and this is also going to be an asynchronous function.
So this is going to be obviously a function which checks whether the user currently logged in user is blocked by any specific user we are loading. So let's open a try and catch block. And in the catch here, I want to return false. So if anything goes wrong, we're going to default to telling them that they are not blocked by that user. And then let's go ahead and attempt to fetch a self using await getSelf which we have imported and then let's get the other user.
So that is await db user findUnique where we have a matching ID. So just in case you don't know, writing ID like this or like this is the same thing. So if they if the key and the value is named exactly the same, you can use a shorthand like that just in case you were confused about that. If we cannot find the other user, let's throw a new error, user not found. So we cannot check whether we are blocked by this user or not.
So we're going to fall back to telling them false. And now let's check if this is ourselves. So if other user id is equal to self.id we can just return false. So why am I even doing these checks? I also do that inside of the follow service.
I also prevent any querying from going on if we are doing an action on ourselves. Well, we technically don't have to do that. Prisma could do that check for us. But this way we don't even do any querying in the database. So we optimize it just a little bit.
So we don't do any unnecessary row reads or row writes if this function is not even supposed to be supported. So that's why we do these checks to break the function early if it's something that we're not even supporting. So make sure you're back inside of your block service and working on the isBlockedByUser function. And after we confirm that this is not the user themselves, let's check if we are blocked. So const existingBlock is going to be await db.block.findFirst where the blocker ID is otherUser.id and blockedId is self.id and then what we're gonna do is return boolean based on the fact whether this record exists in the database or not.
So if it exists we're gonna return true, you are blocked. If it doesn't exist we're gonna return false and we ensure that this is a boolean by adding this two exclamation points here. So now when you use this function you can see that it is a promise which returns a boolean. And if you didn't add those two, well, you could probably still work with it, but you can see that now it's a promise which returns an entire object or a boolean. So it's just kind of weird to work with.
So this way we save ourselves some types here. And I believe that we can actually improve this. Instead of using find first, I think that we can use find unique, but then we have to use a specific type. I believe blocker ID and blocked ID. Yes, like that.
And then inside we can pass BlockerId to be OtherUser.id and BlockedId to be Self.id. So what's the difference between FindUnique and FindFirst? Well, both of them can kind of serve in the same way, right? But find unique in this case will use an index on that unique constraint which we just created. So this is going to be a faster query rather than the find first where it would go through all of the records until it finds the matching one.
Whereas this one will use the index which was created by the unique constraint. And I believe we can also improve that in our follow service but we're not going to do that now. But yes here we also do find first. If I remember I'm gonna come back to this. I want to make sure that we test it out that it's actually working properly.
So I'm just gonna leave it like this for now. For this module I will just focus on the blocking features here. So let's go ahead and leave it like this for now. And that wraps up our isBlockedByUser. What I want to create now is the function to block and the function to unblock the user.
So let's write export const blockUser with capital U to be an asynchronous function, which accepts the ID of the user we are trying to block, gets the self from await getSelf, checks if self.id is equal to the ID of the user we are trying to block, and in that case we are going to throw an error cannot block yourself, and this way we are optimizing our application so no unnecessary queries or rows, reads or writes are done beyond this point. Now let's go ahead and get the other user we are trying to block. So await DB user find unique, where we have the matching ID of that user. If there is no other user, let's throw new error, user not found. What I wanna do next, after we confirm that this user exists, is check if we already have blocked that user.
Const existing block is await db block find unique, where, and let's use that blockerId and blockedId constraint here. So blockerId is self.id and blockedId is otherUser.id. So if we already blocked this user, we're going to throw an error because this is a function to block them again right so if existing block is true throw new error already blocked and now let's go ahead and finally create this block function if none of the above is true. So const block is await db block create data blocker id is self.id and blocked id is other user.id and I also want to add an include here so in our success message we can clearly write who we just blocked. So we're going to populate the field blocked and that way we're going to get the user which was blocked because blocked is gonna be our user.
You can see in here blocked field has the ID, username, image URL. So that's our user relation. And just return block at the end. And now Let's go ahead and wrap this up by creating an equivalent unblock user. So export const unblock user, again, an asynchronous function, which takes in the ID, which is a string.
Let's get self using await get self. If self ID is equal to the ID we are trying to block, let's go ahead and throw new error here. Cannot unblock yourself. Basically we cannot either block ourselves or unblock ourselves. If the ID is the same, we no point in running the function further.
And now let's go ahead and get the other user using a wait db user find unique where we have a matching ID inside. And let's go ahead and check if we don't have the other user, throw new error user not found. Otherwise let's go ahead and check if we have an existing block for this user or not. So const existing block is await DB block find unique where let's use our unique constraint here so if you're not getting this out to complete here I probably should have mentioned this earlier right if you're not getting this out and complete which we are using it inside of this unique find unique right So the reason this exists is because of our Prisma schema. So in here inside of our block model we define a unique constraint on blocker ID and blocked ID And that's what creates an index and allows us to do this querying by blocker ID and blocked ID.
You can see how it generated a combination of those two. So go back to the unblock user function here. And here we are looking for an existing block. So let's give the blocker ID to be self ID and blocked ID to be other user dot ID. And if we don't have an existing block, so the opposite of what we check in the block function, so make sure you put an exclamation point here.
That means that this user is not blocked. So we cannot unblock him. So we're going to throw newError.notBlocked. And now finally, let's go ahead and write const unblock to be await db block.delete and let's go ahead and pass in where id is existing block.id and let's also include the blocked person Well, previously blocked person, right? So we can tell in our toast message who was unblocked and let's return unblocked.
Great! And later we're gonna have one more function in here which is gonna be to load all blocked users but we're gonna do that later when we get when we get to the part where we actually need this information great so we finished our blocked service for now. Now let's go ahead and create our server actions which are going to use this block service. So let's go inside of actions and create a new file, block.ts. And in here, let's write export const on block to accept the ID, which is a string.
And inside of here, let's go ahead and let's write const blocked user to be await block user from lib block service. And let's just pass in the ID like that. And then what I want to do is just simply write revalidate. We can do the same thing that we did in the following action. If we have the block not block, oh, almost made that mistake.
So make sure you're not writing block user when you're doing this. So this should be blocked user, this constant right here. In my unfollow function, I used the equivalent of block user and then it was very confusing because this is a function. So this is blocked user, make sure you use that. So if we have blocked user, what I wanna do is I wanna call revalidate path here from next cache and I'm gonna go ahead and revalidate that user ID here so let's go ahead and call slash blocked user dot not blocked user dot blocked dot username like that and let's also call the validate path on the root as well and let's return the blocked user great and now while we are here let's also create an equivalent to unblock the user so we can just copy this here let's name this on unblock let's call this unblocked user let's await unblock user from our lib block service So make sure you import the unblock user function here.
And then we're going to work with the unblocked user. So if we have the unblocked user, use the unblocked user username and return the unblocked user. So what I'm going to write here is a to-do list, adapt to disconnect from a live stream and to do allow ability to kick the guest, right? Because if we are trying to, we're gonna reuse this function later when we implement our chat right so what that's supposed to do is a block the user in our database but also immediately kick the user from the screen from the stream and disconnect them so they're not gonna be able to see the stream immediately, right? The moment the stream creator clicks the block button, they're immediately disconnected.
But we will also allow guests to look at the stream, meaning people that are not logged in. So this function will most likely break. So we're going to have to come back to this later and adapt it. But for now, this is just fine. What I want to do now is I want to go back inside of my username page.
So that is, make sure you save this file of course go inside of app browse Username page right here and in here we have is following now Let's go ahead and let's add is blocking and do a weight is blocked. So this is is blocked by user actually yes, so Okay, we're not we're not gonna do anything here. This is what we're gonna do. We're gonna go remove this import, you're not gonna need this, Yes, because we don't have this isBlocked. We can actually create that function.
Let me go ahead and see maybe we can create because we're not gonna need it. But just for this example, maybe it's better that we create that function. So this is isBlockedByUser. But how about we create an equivalent one for if we blocked a user right but let's leave it like this for now this is what I'm going to do. So inside of my actions here I'm gonna go ahead and add two buttons here.
So go ahead and add a fragment here and I'm gonna add one button below this which is going to say block user, right? Or you can just write block here. And let's go ahead and run our app. So npm run dev here. And let's go inside of localhost 3000 here.
So we're not gonna look at this by any logs, but what we are gonna do is we're gonna look at our Prisma Studio. So let's also open up Prisma Studio. So npx Prisma Studio, that should be running on localhost 5555 here and make sure you're logged in now go ahead and click on a random user and we should be having the block button here great and now Let's go ahead and do the following. So let's add a const on block here and let's actually call it handleBlock because we already have a function called we already have a function called onBlock in our actions and in here I'm just gonna reuse the startTransition and I'm gonna call onBlock from actions block so go ahead and import that. So this is of course just a test component where we're trying out our services now and inside passing the user ID and let's add dot then here let's get the data and let's call toast.success and let's write blocked the user data blocked username.
And let's also add .catch, post error, something went wrong. Like that. And now let's go ahead and give this button a non-click to be handle block and disabled is pending here. And now let's go ahead and give this button a non-click to be handle block and disabled is pending here and now let's go ahead and see if this is working so inside of my Prisma Studio here right now I have nothing inside of my block table right it's completely empty but if I click block here it looks like something went wrong so let's go ahead and see what exactly went wrong so did we call the correct function actions block inside of here on block so the way I'm going to debug this because I don't believe we got anything in our terminal yeah it looks completely empty what I'm going to do is I'm going to wrap this on block inside of try catch so I'm going to write try and then I'm going to write catch here I'm going to get the error and I will write console.log error. So this way we can debug what's going on here.
So I'm gonna open my terminal and I will click bulk again. It looks like we are immediately getting an error. It could be that I wrote something wrong in here. Okay, let's see why. Let's do this without this catch functions.
What does that do? Does that give us an error in our terminal? Does it add something in our database? It doesn't, so it's not working. Something is wrong here.
Let's go ahead and check the on block so we pass in the ID and we await block user here and we pass the ID here but we're not getting any errors it seems so I'm gonna add a console log here oh I think I know what it is yes so inside of our actions we did not add use server that's what we forgot that's what important with server actions So it's good that this didn't work because otherwise we could have spilled our API secrets somewhere in the bundle. Let's try this again. So what I'm gonna do now is I'm gonna go ahead, I'm gonna bring back this actions because I still wanna debug this. How come this is not working? How come data is possibly undefined here when I'm doing the same thing for the following here so let me check my on block function again so oh I think it's because I wrap this inside of try and catch so if I remove try catch yeah then it works so I'm gonna remove my try and catch now I added the user server here so make sure you do that I'm just gonna save this to be exactly as it was from before and I'm gonna go ahead and prepare my Prisma Studio again.
So now it says block. I will click block and let's see. Block the user something else. Perfect! And if I refresh here I should have the model block and I do.
And in my users model I should have the equivalent relations here. So let's see. Blocking this user which is my currently logged in user Antonio seems to be blocking someone and the user above is blocked by someone which can only be the other user in my database so this is officially working. What I want to create now is just change this to be unblocked so I'm not going to do anything complicated I will rename this to unblock and I'm gonna go ahead here and I'm going to write, well, I'm just gonna call the on unblock, right? From actions block.
So make sure you import on unblock here so we're just testing our functions and in here I'm going to write unblocked the user right let's try it out now so I'm going to go ahead in my Prisma Studio I have my block model which should be deleted after I click unblock here. So let's try it out and it says that I've unblocked that user and when I refresh here, there we go. You can see that now we no longer have any blocking relations meaning that these users are officially not blocking one another. Perfect! So confirm that this is working for you.
What we have to do now is we have to modify our recommended service and our following service to exclude blocked users from the list, right? So we're going to do the following. Just leave this as it is for now, this is just our test component. And let's go back inside of our lib, inside of the recommended service. And in here we already started doing some combinations on our queries so alongside this followed by let's also go ahead and let's add another not blocking some blocked id user id so when we load our recommended list this way we ensure that none of those users in that list are blocking the currently logged in user right and I also want to do the same thing inside of my follow service.
So let's go inside of the follow service here and in here we have getFollowedUsers so even if we follow them, if they blocked us we are no longer going to show them in the following list because there is going to be no way for the user to look at that user's stream. So inside of this WHERE clause alongside the follower ID, we're going to add if the following user has a blocking relation and it's supposed to be none for the blocked ID self ID. So we ensure that the user we are following is not blocking that user. Great! So in order to test this out we have to do the following.
We have to create a block, we have to initiate the block action so let's go back inside of components actions here and change this handle block to again use the on block right So we already have this imported in here. So make sure this time you use onBlock as we did the first time and I'm gonna rename this function to block. Then make sure in your database that you don't have any block relations right or if you do you can just leave it as it is or just initiate an unblock function. Basically, it's going to be easier to follow if you have the exact same thing that I am. So I have nothing inside of my blocked here and my users have no relation when it comes to being blocked by one another.
So now I, as Antonio, am going to block this user here. So I'm going to go ahead and click block here, like this. And my toast message I think stayed the same and now in here there we go I now have one blocked user so what I'm going to do now is I'm going to log out and I'm going to enter as this user and this user should no longer be recommended right here. Right? So let's go ahead and log out now.
And now I'm going to go ahead and log in with the other user. And as you can see, I have nothing inside of my recommended or my following tab. Because that user is blocking, right? And here's what we can do now. So I know that my other user's username is Antonio from my database, right?
So I'm gonna copy this username. I'm gonna go manually to localhost 3000 slash Antonio. And this is what I can do now. So since I know that I am blocked by this user we can go inside of username page.vsx and we can write the const isBlockedByThisUser oh wait, isBlockedByUser so that's why I named this isBlockedByThisUser because our function name is isBlockedByUser which you can import from block service so I'm just gonna move this here and let's go ahead and pass in the user.id so isBlocked by this user or we can just do isBlocked so isBlocked, oh wait, isBlocked by user and in here I'm gonna write a paragraph which says is blocked and let's go ahead is blocked by this user and we're gonna go ahead and open back things like this and render is blocked like this and it should be rendering true and it does as you can see right here it says is blocked by this user that we are trying to load is true and what we're going to do later is very simply in here we're going to check if is blocked in that case we're just going to render the not found function which we already used here.
So now when someone tries to manually visit the blocked user as we just did right now, they're going to get a 404. So we're going to completely prevent the blocked person from finding this user anywhere. Great. And I'm just going to remove this for now just in case we need to come back to this and do some more testing. Perfect, and just to confirm that this is still working with backwards compatibility, what I want to do is log out and as you can see here we have both users now I will log back in with the other user and I will unblock myself so here I am in the other user and in here what I'm going to do now is I'm going to unblock myself.
So let's go ahead and just go back inside of the actions and now we're gonna use this on unblock function here. So let's just replace on block with on unblock here. Make sure you save that. Let's click on this. Now it says block but you know what the code is actually doing.
And now in my database I should no longer be having this block relation again. There we go. And if I log out and if I go back inside of the other user now, there we go. I am fully unblocked and I can visit this user again and it says, is blocked by this user, false. And you can see that it was the same user.
You can see my URL that it says Antonio, which I previously went to manually and it clearly said that I was blocked by that user. And you're probably noticing that once I block a user I allow the user who blocked that user to see their profile but I don't allow the opposite right So that's my design choice. You yourself can go even further and make sure that those two users never see each other, right? So I'm imagining the block function to be more used as block this user from my stream, right? But I still want to be able to see that user in my homepage but that user that I blocked should not see me anywhere on the page.
So that's how I imagined the block system. You can of course modify the code if you like it some other way but I would recommend that you keep it this way until you finish the tutorial and then when you finish go ahead and play around and make it your own. Great so we now finished our blocking service and now what we're finally ready to do is start creating our stream model which is going to control whether we are live or not and we're gonna go ahead and create this little user dashboard where we're gonna control the settings of the stream and then we're gonna be finally ready to create a live stream connection and some real time chat. Great great job.