00:02
Okay, before we move on to our organization, we have one more card for our account settings and that's gonna be the account deletion card.
00:11
Similar to how GitHub asks you to enter in the repository name whenever you attempt to delete out a repository, we're gonna do something similar and ask our users to enter in their email address
00:21
in order to confirm that they actually wanna delete their account. So let's go ahead and start by getting that card added in. So we'll jump back into our text editor and we'll scroll down to where we have our components.
00:30
So inside of our inertia components, we can right click this new file and we'll call this our account delete card.vue. Similarly to how we called the other one account email card.
00:40
We'll have a script set up with a lang of TS and we'll also go ahead and get our template added in there. Just like we did with our email updates,
00:48
we'll want to take in the user's email so that we can add it into the placeholder of this confirmed deletion input. Then we'll have our const form equals use form
00:58
from inertia view three. And we'll just have a single email field here that will default to an empty string. We don't wanna default that to using our email prop because we want the user to actually enter in their email
01:08
to confirm deletion. Now, as for our template content, it's gonna be relatively similar to our account email card. So let's go ahead and copy all of the card contents from that and let's jump back into our deletion
01:18
and paste it in. We will go ahead and get rid of our password field. We won't need that for account deletion confirmation. We just need that email. And let's go ahead and update our text accordingly.
01:28
So this one will be for deleting the account. Our text description for that will be delete your account and all your data.
01:37
We need to change the forms ID as this will reside on the same page as our account email form. So this will be our account delete form. And we can go ahead and jump down to our buttons form
01:47
attribute and update that accordingly. While we're at the button, we can switch that from update email to delete account. The loader and all of the other contents there will remain the same.
01:57
And then we need to switch from a put to now a delete and the URL will simplify to just settings account. Now, since they'll be deleting their account,
02:06
we're gonna redirect them away from this page. So we don't need to worry about any on success handler there. Then we'll just update our form input label to be a little bit more descriptive.
02:15
Please enter your account email to confirm deletion or something of the sort. And that really should do it for our account delete card.
02:25
So we should be able to jump back into our account settings page and add this in just underneath our account email card. So account delete hard,
02:33
again, passing that user's email in as the one and only prop. So if we give that a save, jump back into our browser, we should now see our card down here. And sure enough, we do.
02:42
Let's go ahead and make our button red here so that we are very aware that that is a destructive action. So jump back into that account delete card
02:50
and add in a variance prop of destructive onto there. Give that a save, jump back into our browser, and there we go. Now it's a nice bright red.
03:00
All right, now in terms of rigging this up, let's jump into our terminal and let's go ahead and make an action for this. So node.ase make action. We'll put this inside of our settings directory
03:08
and call this our destroy user account action. And we go and clear that out. And now jump back into our text editor and let's scroll on up now to our actions, go into our settings directory
03:18
and our destroy user account action. All we need for this is our user of type user model. We can go ahead and extract that user out of our handle method. Now, as for what we need to delete here,
03:27
our user is bound to the organization and then almost everything else is bound directly to the organization and not to the user. So destroying a user, all that we need to worry about
03:37
is deleting the link to the organization itself, which is going to automatically cascade whenever we await user and call the delete method.
03:45
However, if there are any organizations where this user that we are deleting is the one and only user bound to that organization,
03:53
that would leave that organization's data dangling inside of our database with no users bound to it. So let's also create a new static async method
04:03
and we'll make this private called delete dangling organizations that takes that user in to delete organizations where this user that we are deleting
04:13
is the one and only user bound to it. So we'll return and let's import our organization model for this. You could also query it directly off of the user, but I think it's going to be a little bit easier to read
04:23
if we go directly off of the organization here. So first we wanna check for organizations where the user that we are deleting is a member.
04:32
So we'll do a where has users and then query, query.where users.id has the user ID that we are currently deleting.
04:41
Then we wanna do the opposite of this check. We wanna do a where doesn't have users,
04:48
query, query.where not users.id is the user ID that we are deleting. Then we go ahead and delete those out of the database.
04:57
Okay, so in essence, this query that we've just written is checking for organizations where the user that we are deleting is the one and only member of that organization.
05:06
Our first where is limiting our results to organizations where this user is a member of them. And then the second where is limiting the organization results down further
05:15
to organizations where this user is the one and only member by checking where it does not have any other users
05:23
who are not the user that we're currently deleting. And just like other changes that we made, we only want to delete those dangling organizations when our users deletion succeeds and vice versa.
05:33
So let's wrap this user deletion in an await. We can import DB from Lucid Services DB and a managed transaction. Let's get our async TRX, wrap that up
05:43
and paste our user delete in. The first thing that we want to do is tell our user to use that transaction. So we'll pass TRX into our use transaction there. Then we can go ahead and await
05:53
this delete dangling organizations, pass that user in. And then we also need to pass that transaction in. So let's go down to our delete dangling organizations method
06:02
and get TRX of type transaction client contract and import that from Lucid Types database. We can pass that transaction into the query method
06:12
as the client, just like so. And it will bind this organization query where we're deleting these dangling organizations to our transaction. And that should do it there.
06:21
Let's next jump into our accounts controller where we have our destroy method. We're gonna want our request, response, session and auth from our HTTP context.
06:31
And the first thing that we're gonna want to do is grab reference to that user. So we'll do const equals user auth use web.user and assert that we do have that user as this route will be auth protected.
06:41
Then we'll define our validator directly inside of our controller method here. We'll use vine. So we'll want to import that dot compile, get our vine object set.
06:51
And this will have our email. Now for our email validation, all that we want to do is relatively similar to what we did with our password verification checks. We just want to make sure that this email
07:00
equals exactly the auth user's email. We don't want to do any additional validations beyond that in case our validation requirements have changed since they've created their account. So we could do vine string
07:10
as all emails will be string based and just verify that that email is in, pass in an array with a single value that is the user's email. Essentially checking to make sure
07:20
that the email provided via our form exactly matches the user's account email. Then we want to run that validation. So we'll await request, validate using
07:29
and pass our validator in. Then we want to await and call our destroy user account, its handle method and pass that user in.
07:39
Once we have successfully confirmed that the user wants to delete their account and verify that the email provided is the one that matches their account as well. And we've deleted their account
07:47
and any dangling organizations they're a member of, then we can go ahead and log them out. Now we wrote this a while ago, so let's refresh our memory here. Let's jump into our auth.
07:56
We put this inside of an HTTP, which means that it's going to inject the HTTP context into it. And let's check out our web log out. So exactly that, this is injectable.
08:05
So what we'll want to do is decorate our destroy method with the @inject decorator from AdonisJS core. And let's use dependency injection
08:13
to get our web log out action as type web log out. Remember on controller methods, those come in after the HTTP context.
08:22
Once we've injected that, we can scroll back down underneath our destroy user account call to await called web log out dot handle
08:31
to log the user out of our application. Once we've gone through this whole flow, the user has been completely purged of our application. So we can let them know session slash success,
08:40
your account has been deleted and then return response redirect and send them to a register show route
08:49
as they don't have an account to log into. So we'll send them to our register page. Give that a save. And we should now be able to test this out. Now I don't want to delete the account that we've been working with thus far.
08:59
So I'm going to go ahead and log out and let's register a brand new account specifically for this. We'll call this delete me,
09:06
give it an email of delete me one at test.com and something for the password. We'll create our dangling organization of I'm a dangling org. And there we go.
09:16
Let's next jump into our account settings, scroll down to our delete account and let's start with an invalid check here. So let's do delete me at test.com.
09:25
The valid email has a one after delete me. Let's try delete account. And there we go. We got an email is invalid. If we now enter in the correct email,
09:34
delete account, your account has been deleted and we've been redirected back to the register page. Furthermore, if we jump back into our terminal, let's jump into a REPL session.
09:43
So node ace REPL, await load models and await models.organization.query
09:50
where I like name contains dangling and do a search. We got back zero results.
09:57
So our dangling organization has been deleted as well. I can do .exit to exit out of that, clear it out. And we should now be all set to move onward.