00:09
they have an organization set up in order to work with. So what we want is a brief onboarding flow that asks them to create an organization.
00:17
So the user would register and then they would be taken to a step that asks them to create their organization to get started. They could give it some name here, then they would hit create organization, and then they
00:27
would be taken into their courses panel for that specific organization. So that's the flow that we're going to be filling out here today. We want to add the intermediary step where they create their first organization.
00:37
So let me close out the finished application here and let's go back into our local host. Our present behavior is that whenever they register they're just taken to the home page
00:45
that came with our Inertia application out of the box. So our first step is to add in that intermediary route that asks them to create their organization.
00:53
So let's go into our routes web file here and let's add an additional router group.
00:59
These are a group of routes that we can define together to apply things to as a singular group. One of the other reasons why I like to disable Prettier and ESLint on my routes is that with
01:09
these groups I like to give them one line of padding at the start and end of them. I just feel with chaining it visually helps separate what's what inside of them.
01:18
And on this group we want to ensure that a user is authenticated in order to access any of the routes inside. So we'll do use and then use one of our middlewares out of our start kernel file and this gives
01:28
us access to our named middleware that we have registered inside of our application. Since we started this application with a stub of authentication we're given an auth and
01:37
guest named middleware. Auth will ensure that the user is authenticated and guest will ensure that the user is not authenticated. So we want to use auth here.
01:46
Inside of this route group let's define a router get for our organizations and this will just be a create path.
01:53
This will be that intermediary step where we ask the user to create their organization. And we'll have this point to an organization's controller.
02:00
Now at present here I don't believe our organization controller is a resourceful controller. Yeah it's just empty. We created this whenever we created our models and migrations.
02:09
So what we can do is we can go ahead and just right click on that and delete it and we'll just recreate it as a resourceful controller.
02:16
So I'm going to jump back into my terminal here go into my empty terminal tab. It's got the action that we had in there.
02:21
We'll clear that out and run node is make controller organization hyphen R and that should create it as a resourceful controller. There we go.
02:30
So now we have it back inside of our application and if we take a look at it there it is. There's our resourceful controller. It has methods for index, create, store, show, edit, update, and destroy.
02:39
Each of those with a resourceful based action tied to them. Cool. So now inside of our routes again what we want to do is bind our organization's create
02:48
to the create method inside of our organization's controller and we can give this a name as organizations create. Let's give that a save and then we can jump back into our organization's controller and
02:58
all that we need to do here is grab inertia and return inertia render and let's have this go to organizations create.
03:07
This is the only onboarding step that we're going to have. So we won't create a separate onboarding section. We'll instead just keep it all housed inside of the organization.
03:15
Now we need to go into our inertia folder down to our pages, right click that, new file,
03:20
create an organization's folder with a create.vue file inside of it. Let's go ahead and get our scripts.
03:28
We'll make that a setup script using TypeScript and for this so that it looks the exact same as our auth flow and doesn't have the additional title bar allowing the user to go elsewhere
03:38
which also requires our organization as well to display, we're going to keep the user inside of our auth layout for this step.
03:45
So we'll define options and specify the layout to be the auth layout. Hit tab to auto import that and we're also going to need a form for this step.
03:55
So do const form equals and we'll use the use form helper from inertia.js for this. We only need one field and that's the name for the organization.
04:04
So we'll default that to an empty string. Then we need our template, then we go ahead and add in our app head component and we'll
04:11
self-close that and specify our title as create an organization and a description
04:17
of create an organization to hold your courses. Then we'll have a div for our little title block, give that a class of flex, flex call
04:27
and a space y of two. We'll have then an h1 with class text to excel font, semi bold and tracking type.
04:36
This h1 will then read out, create an organization, this will be the title for the page and then we need a little bit of a subtext, you can make that small, muted.
04:45
So we'll do text muted, let's do foreground there and this will say to get started, you'll
04:52
need an organization to hold your courses. You can have as many organizations as you want. All right.
05:02
After that div, we'll have our actual form. We'll do at submit, prevent and we'll use our form from the use form helper to send
05:09
out a post request to slash organizations to create this actual organization. We'll break that up here next.
05:18
Inside of this form, we'll have our div class, give this a class of grid with a gap of three
05:24
and then we'll use our form input component and self-close that, give it a label of name
05:30
and we'll tell it to use the view model of form name and provide it the error prop form errors name. Lastly, we need our form submission button.
05:40
So underneath our form input, let's add in a button and we'll make this disabled if our form is processing. So if it's in the middle of sending out the request and it hasn't received the response
05:50
yet. Additionally, we can also show a loader whenever our form is in that state as well and we can import that from Lucide view next.
05:57
We'll do a VIF on that form dot processing and give it a class of margin right to height of four width of four and tell it to animate spin.
06:07
Then the actual button text will just be create organization. That should be our intermediary page done.
06:14
Now we need the actual form post route to handle this submission accordingly. So we'll jump back into our web routes and we'll define an additional organization route as router.
06:23
And this will be a post to just slash organizations. This will reach again for our organization's controller and it's store method.
06:31
And we can give this a name of as organizations store. Now you may be wondering why are we giving these routes names if we're not going to make use of them inside of view.
06:41
They are still handy whenever we need to reference them anywhere inside of our AdonisJS application, which is why I'm giving them names here.
06:48
But should we ever want to refactor our front end to also use these names, then we already have them defined and readily available for us to do so.
06:55
And while we're defining our routes, it doesn't take much time to just tack a name on there as well. Okay. Let's go ahead and give that a save.
07:02
Now jump back into our organization's controller and let's fill out the store method. So the first thing that we're going to want to do is create a validator.
07:09
So we can do const data equals await request dot validate using, and we need a validator to pass into there.
07:16
So we can go ahead and hit command shift P if you have the AdonisJS extension installed inside of Visual Studio code, and we should be able to search for make validator and we
07:24
can hit enter on that and it will ask for a validator name. Similar to auth, we're going to house all of these validations resourcefully inside of a single folder.
07:32
So we'll have one validator for our organizations, another one for our courses and so on and so forth. So we'll go ahead and create one there for our organization and it will take us directly into here.
07:42
So we want to export const and we'll just call this our organization validator as we'll be able to make use of it both for creating and updating.
07:51
So we'll do bind compile and the only thing that we need here is a bind object with that name property on it.
07:57
And for this we'll do bind, it's a type string and let's apply a max length of 100 to match our database there.
08:04
We'll also ensure it's required, so we'll leave optional off of it. We'll give that a save, jump back into our organization controller and now we can make
08:11
use of it via our organization validator and hit tab to auto import that. Next let's create an action to actually take this data and create our organization.
08:20
So let's jump back into our terminal here and clear that out, node ace make action. We'll put these actions inside of an organizations folder and we'll call this one store organization.
08:30
We can hit enter there. We'll leave HTTP off of it this time so that it is not bound to the HTTP context.
08:36
Okay, if we scroll back up now we should see inside of our actions a new organizations folder with our store organizations in here.
08:43
For this we're going to need to take in our user and we'll be able to get that just from the authenticated user because the user will be authenticated at this point in time.
08:51
So we'll take this with a type of our model user and then we also need that data and we'll infer from our validation. So pass in our organization validator there.
09:01
Inside of our handle method we'll go ahead and extract both of those out. So we'll take in our user as well as our data and we're going to have a number of operations to perform.
09:09
So let's ensure it's all succeed or none succeed by wrapping it inside of a transaction and for this we'll use a managed transaction.
09:16
So we'll return db and I don't see that coming through in my autocomplete so we will import that.
09:23
So import db from at adonisjs slash lucid services db.
09:30
With that imported we can now call transaction and we can wrap everything that we're going to do inside of a callback function of this transaction and this callback function gives
09:38
us back an instance of the transaction to work with. The very first thing that we're going to want to do is create our organization.
09:45
So that's our first step and we can do that relatively easily with our model by doing const organization equals await organization.
09:53
We can import that model and call dot create passing in our validated data and then on
09:58
top of it we want to pass in an option with the client set to our transaction so that it actually uses our transaction for this database operation.
10:08
Next since this user is the one creating the organization we're going to make them an admin. So make this user be admin.
10:16
Let's keep our handle method clean and extract this out into a separate method inside of the store organization action. So we'll do static async.
10:24
We can make this a private method using the vanilla JavaScript hash naming convention for the private method there and call this assign admin.
10:32
It will just take in that organization that we've created as well as the user who is the authenticated user.
10:39
For this we'll want to return from that organization we'll reach for the relationship to our users.
10:45
Via this relationship we want to attach a new binding using the user ID so we'll reach
10:51
for the user ID and apply an intermediary table data or the role ID setting the role to our roles.
10:59
We can import our enum there and reach for our admin role. Then we want to call that method.
11:03
So we can await this assign admin pass in our organization and the user. Let's step through exactly what this is doing real quick.
11:12
So from our newly created organization we're reaching for that relationship that we've defined to our user which is a many to many relationship.
11:19
In that many to many relationship we have an organization ID a user ID and a role ID.
11:25
If you don't have intermediary table data to provide you can merely just call attach and pass in the relationship ID.
11:32
So we could just pass in our user ID and I believe that needs to be an array like so. And that would then bind our user to the organization via our organization users pivot table.
11:42
However we also need to define that role ID which is why we don't want to just provide
11:47
an array but rather an object where the key of the object is the ID that we want to bind
11:53
in which is our user ID and then the value for that object key is any intermediary table
12:00
we want to add in to that record that it will create and that is our role ID.
12:05
So whenever we run this it will take the organization's ID the user's ID and any data that we provided
12:12
here and add it to that record in our intermediary organization users table. Okay so an additional thing that we want to do is whenever a user creates an organization
12:21
we also need that organization to be stubbed with the default difficulties statuses and access levels just giving them a starting point to begin with.
12:30
Just like we did with our assigned admin we can perform all of those inside of a separate method so we'll do static async make this private as well and we'll just call this create defaults.
12:40
We'll take in our organization of type organization and we can return an array of promises.
12:48
Ultimately what we're going to want to do here is await reach through the organization via the relationship for our difficulties statuses and access levels and we'll just want to create them.
12:58
Now we can make this simple on ourselves by defining all of the defaults in one central location and then just pass them into the create mini call here.
13:06
We can do that for our difficulties our statuses as well as our access levels to keep the
13:13
actual logic here relatively clean and a really good place to house all of this is inside of our applications configuration.
13:20
So we can just go ahead and create a new file inside of our config and we'll call this organization.ts
13:27
and from here we'll export const defaults and set this to an object.
13:32
We'll have our access level defaults which will be an array our difficulties defaults
13:38
which will also be an array and then our statuses and we'll define them all inside of this object so that we have direct access to them.
13:46
For our access levels we'll need a name and we'll have the first default be free. These are going to be bindable to both our courses as well as our lessons.
13:55
So you could specify something like a course is free or it's paid and you can do the same at the lesson level there as well.
14:01
So for our free one we'll give it a color and we'll give it a color of 6EE7B7 and we have our is default flag we'll set that to true and then we have the order field which
14:11
will set this one to zero. Then we'll have one more which will be paid we'll give this one here a color and that'll
14:18
be 67EAF9 and then the order of one. The default for the is default flag is false so we can omit that altogether for the access level.
14:29
Onto our difficulties now for these we'll have one for easy the color will be 6EE7B7
14:36
the is default flag will be true and the order will be zero. I'll go ahead and give that there a copy and a paste a paste and a paste because we will
14:46
have four default difficulties set the order to one there two there and three for that one.
14:52
We'll go from easy to medium from medium to hard and hard to expert.
14:58
The color for medium will be 67E8F9 the color for hard will be A5B4FC and the color for expert
15:09
will be F0ABFC and for all but easy we can go ahead and remove that is default flag and there we go.
15:16
Lastly we have our statuses now we're also going to have four for that so we can just copy all of our difficulties use that as a good starting point there and paste that in.
15:25
The first status is going to be not started then we'll go from not started to planned
15:32
from planned to recorded and recorded to edited and I can't count I need one more there we go and then from edited to done.
15:40
The color for not started is going to be CBDFE1 that's not a one there we go then F0ABFC for
15:49
planned and then A5B4FC and then edited will be 67E8F9 and lastly done 6EE77 okay oh we
16:02
also need to increment done and it's order by one there too. So there's all of our defaults we can now jump back into our store organization we can
16:10
import our defaults so we'll search for defaults there and there is as the second item our
16:16
organization defaults we can import that and we should be able to do dot and get access
16:21
to our difficulties for our difficulties relationship we'll do defaults dot and I believe this one's
16:27
our statuses and then we have defaults dot access levels like so.
16:32
Lastly we need to call that so we'll await this create defaults organization and there
16:38
we go then we'll want to return our organization which anything that you return from a managed
16:44
transaction will be returned from the transaction itself so by returning our organization here
16:50
we'll also be returning our organization here and whenever our code flow exits the callback function the managed transaction will commit the transaction if any errors occur inside
17:00
of this callback function then the managed transaction will roll back the transaction meaning that we don't have to wrap this inside of a try catch to do that manually.
17:09
Now neither of these two calls deal with one another they don't need each other in order to operate so we can have them happen at the same time right now since we're awaiting
17:17
both of them they will happen one after another but if we remove that we can instead do const
17:22
role promise equals leave the await off of there and then we can do const defaults promise
17:29
equals and leave the await off of there now we have access to the actual promise return
17:34
for both of those so we can await promise dot all pass in our role promise as well as
17:41
our defaults promise there and actually I think we need to wrap that in an array so
17:47
yeah there we go now just like we did here none of our defaults require one another either in order to operate so we can do the exact same thing here but we can make this a little
17:56
simpler on ourselves and just instead return an array of these promises so we just wrap
18:02
all of these in an array like so and get rid of the awaits on them so that we're just returning
18:08
back the promises for each so now we'll have an array of promises being returned back here
18:14
so inside of our actual handle method we can just spread that array of promises into our promise dot all and right now we're getting a squiggly on our default promise and that's
18:24
because we're returning back a promise of promise because our create defaults is an async method if we get rid of that async we'll now just be returning back these straight
18:33
promises inside of our array rather than a promise wrapping the array of promises okay now that's happy so we should be all good to go so we jump back inside of our organizations
18:43
controller do const organization equals await call our store organization action and its
18:51
handle method and we need to pass in our user so from our store we'll also want to extract
18:56
out off and we can pass in user as off dot use web dot user and we will have an authenticated
19:03
user at this point so we can assert that as well as pass in our data there now the end goal that
19:09
we have still is not done we want to ultimately redirect them back to their courses index page
19:14
but we don't have that at the moment so let's return response and we also need to extract
19:20
response out of our HTTP context here dot redirect and we just point this to our path
19:26
of the home page which will serve it instead for now we will need this organization down
19:32
the road in a couple of lessons so we'll leave this as we have it at the moment right now okay so our organization should get created now the last thing we need to do is jump back into our
19:42
register controller and change where we're redirecting here we no longer want to redirect
19:48
to that home path but instead to our route or our organization's create step so that the user
19:55
registers they then see create your first organization and then once they do that we
20:01
can take them into the application so let's jump back into our browser and give this a go so for a full name I'll just do on board one on board one at test.com and something for the password
20:11
hit register and okay that did not quite work it almost looks as though we didn't get anything back
20:18
as our response so let's hide our browser back away and let's take a look at our organization's
20:24
controller and well yeah that's gonna be the issue I accidentally did this inside of the index method
20:30
which is where we would list organizations and instead we want to do this inside of our create
20:36
method rather than index okay we can also go ahead and just get rid of that index method because we should not be in need of a full page to list out our organizations that's going to be happening
20:46
inside of our navbar okay so with that correction made let's try this one more time let's jump back
20:51
into our browser let's hide that invalid response pop-up that we got there away and the fact that
20:57
it tried to redirect us somewhere probably means that this user already now exists and if that user
21:02
exists we're probably also logged in so let's jump over in a new tab do localhost 3333 which will
21:09
take us to our home page and let's just make sure that we're logged out by clicking log out okay we're done with that tab so we'll close that back out and now let's just switch these ones to twos
21:19
just like so and leave the password the same and let's try registering one more time there we go okay so we got our welcome to plot my course toast message down there and we're
21:27
taken to our create an organization step if we now type out something here hit create organization
21:34
voila we're now redirected back to our home page and at this point we should have an organization and we haven't completed our drop down that we'll eventually get to so let's jump back into
21:44
our terminal node ace repl and await load models well await models dot organization dot query dot
21:53
poyo hit run on that and sure enough there is our something organization so we can do const
21:59
org equals await models organization dot find or fail with an id of one run that and now we can do
22:07
await org related users dot query dot poyo run that and sure enough there is our on board two
22:15
user and additionally we can see our pivot data for this user as well where the organization id
22:21
is one the user id is four and the pivot role id is two which is our administrator so everything seems to have worked perfectly there so let's go ahead and run that