00:02
- All right, so now that we have our courses search functionality done, we're ready to move on to our lesson search.
00:10
So if we dive into our controllers API version one lessons controller, since we do not have an index method, we can go ahead and plot this at the top of our lessons controller here.
00:19
So let's do async, call this search, take in our request and the organization from our HTTP context. And the very first thing that we're gonna wanna do
00:28
as always is authorize and we're reading here with our organization to make sure that the access token has permissions to perform this operation. Once we have authorized this operation,
00:38
what we then need to do is validate the data provided in that we're searching with. So let's go down to our validators and jump into our lesson file. And keeping with the ordering that we have inside of our controller,
00:48
let's go ahead and put this one first. So let's export const lesson search validator equals vine compile. And inside of here, we can do vine objects.
00:57
And just as we did with our course, we're gonna want the page. So that's gonna be a vine number and we want that to be positive. And we can also make it optional so that they don't need to provide it.
01:07
And then we'll also accept in a per page of type vine number. And with our course, we used min and max as separate rules. So min, and then there's also max,
01:17
but there is also a range that allows us to provide in an array where the zero index is the min number and the first index is the max number. So we can do one as our min and I'm gonna keep our max at five,
01:27
which is what we were testing with with our courses. But again, in production, you're probably gonna want that to be a bigger number than five. That's just gonna keep our payloads nice and small so that we can easily test things out here in Hopscotch.
01:37
Okay, beyond our pagination properties, we can go ahead and peek down at our lesson validator to get a sense of the properties that we have to filter by for our lessons.
01:47
So we have a name, we have a publish at, which is a date time format. We're gonna bypass filtering by this in this lesson, and we'll focus on that in the next lesson.
01:56
But beyond that, we also have a module ID, an access level ID, and a status ID. So our name property is going to be relatively similar to our course name.
02:05
We can use our string filter and apply its rule there, allowing us to do our starts with, ends with, not starts with, not ends with, all of those fun combinations.
02:14
And we can go ahead and add in our access level ID using our number filter. We can use that rule there. And then we also have the status ID,
02:23
which too can use the number filters rule. As for the module ID, it's really up to you on whether or not you want to allow just a single ID to be filtered by.
02:32
Does it really make sense to allow an array of modules to be filtered and searched via its lessons? Now that might be useful. So we're gonna go ahead and add it in here,
02:40
but you could also just do that as a simple number or anything else that you feel makes sense there. So for our module ID, let's go ahead and also use
02:48
our number filters rule for that as well. All right, let's give that a save. Jump back over into our lessons controller and let's validate our data. So let's take in our page, our per page,
02:58
and then we can spread in the rest as our filters. Equals await, request, validate using, and we want to use our lesson search validator there. Once we have our data validated,
03:08
we're ready to move into our action next. So let's go ahead and jump into our terminal, node ace, make action. We should have a lessons folder here
03:16
and we can add in our search lessons action to it. Once we have that created, we can clear that out, jump back into our text editor, and let's scroll on up to our actions. There's our lessons folder
03:26
and there is our search lessons action. Okay, let's first focus in on our type. So if we remind ourselves what we have here for our search courses, we have our query with the type of HasMany Query Builder contract
03:36
of type of course. We have the data from the validator. We have our filters, which omits the pagination information from that validator. And then we have our traditional params
03:46
passed into the action, which is the organization, those filters, page and per page. So we're gonna want all of those to be relatively similar for our lessons as well. So let's go and give them a copy,
03:54
jump back to our lessons search action, and let's replace our params with all of those. Before we fix our imports,
04:02
let's replace our course here with our lesson instead, since that is what we are working with in this action. And we also wanna replace the validator
04:10
from our course search to our lesson search validator. Once those are all set, we can go up to any of the red squigglies remaining, command dot, and add all missing imports to fix the remainder.
04:20
All right, let's jump down to our handle method, grab our organization, the filters, the page, which we can default to one if it's not provided, and our per page, which again,
04:30
I'm gonna default to five if it's not provided to keep that payload small for now. For our query, we're gonna want to return, use our organization to reach through its relationship
04:40
to our lessons, and then grab a query builder off of it. Along with our lessons, we're going to probably want to provide in
04:48
that lessons access level, as well as its status. You may also choose to provide in its module and or course information as well,
04:57
though those are optional. But if you wanted to provide them in, then it could preload in our module. Oh, looks like I attacked an S on that relationship.
05:05
So let's go dive into our model for our lesson. And down here at the bottom of the file, we can indeed see that I have a belongs to, which is just a singular relationship,
05:15
but I attacked an S on the name. So let's just get rid of that S there, jump back into our search lessons. Let's also jump into our terminal where we have our server running,
05:23
just to make sure that nothing is complaining there. So that's probably the one and only place that we're using it is right here. So switch that back to module. And now if we want to provide the course information
05:33
on top of the module, then we could preload in by providing in a callback to the preload, another query.preload, now chaining off of the module to our course.
05:42
So what we've done here is now we'll have an array of lessons come back, and each lesson is going to have a module property. And inside of each module is going to be course information.
05:51
So that might be a little bit more than what you're looking to return back, but that is how you could do it. So this would be something like lessons,
05:59
and then you can grab something like the first index.module.course is kind of the preload chain that we have going on there. You could also put it upon your users to specify with the filter,
06:09
whether or not they want that included as well by adding a select list to your filters itself. More on that later.
06:16
For now, let's go ahead and add in an order to our results, and let's provide an array so that we can order by multiple properties. First, let's try to order by the column publish at,
06:26
and we'll order this in descending fashion. Descending with dates is going from now backwards,
06:33
ascending is going from say 2000 to now, 2025. So it will be newest published to oldest publish there. If we don't have a publish date,
06:42
then we can provide in a second item to our array, ordering by maybe the updated at field. And again, we can do that in descending fashion
06:49
to get the most recently updated to the oldest updated. We can also specify how we want the nulls to be ordered with this as well by tacking on a nulls property
06:58
and setting its value to whether we want them to be first or last. I'm gonna say, let's go ahead and put them last for both. So we can do nulls and plop them last in our results.
07:08
Finally, we have our pagination. So let's call our paginate method, pass the page and per page into it, give that a save. We will circle back here momentarily to fill out our filters
07:18
but let's go and jump into our lessons controller and finish the controller out. So let's do const lessons equals await, search lessons and call our handle method,
07:27
passing in the organization, our filters, our page and per page. Then we need to set up our base URLs.
07:36
So from our lessons, we'll set the base URL. And in order for us to pass anything into this, we need to have that URL defined.
07:43
So let's do version one and just beneath our search courses, let's add in our search lessons. So router. Again, we're gonna do post/search/lessons
07:53
and let's use our lessons controller and its search method as search lessons. Give that a save, jump back into our lessons controller.
08:02
And now we can import router from AdonisJS course services router and make URL search.lessons. We also need to do API version one,
08:12
prefixed in front of that to use our API route definitions and the version one for that. And then we should be good to go ahead and return back our paginated lessons.
08:21
Let's test it out real quick, make sure that it works. So within Hopscotch, let's add in a new request for search lessons.
08:29
Give that a save, switch this to a post, switch the URL to search/lessons and we don't have any filtering going on. So I'm just gonna go ahead and leave the body empty,
08:39
send it off just to make sure that we get a response back. And sure enough, we do, we have four lessons, five per page, that's always going to be one. But nonetheless, we get that back
08:47
and our page URLs are pointing to the correct URL. So back within our text editor then, let's apply our filtering into our search lesson action. And let's set this up similarly
08:57
to how we did our search courses. So we'll do a static private method called apply filters and we'll take in our query, that's of type query,
09:05
which we have set to our HasMany Query Builder contract using our lesson model. And then we also wanna take in our filters of type filters,
09:13
which we have set to the lesson search validator type, omitting the page and per page options. Within here, we want to use our string filter
09:22
to build out using our query, a where statement for the name column, searching for our filters name value.
09:30
Then we can do the same thing here with our number filter, build that as well, passing in the query and we can search against our status ID using the filters.statusID.
09:40
We're gonna wrap this in an if, so we actually don't need the question mark on our name there. And then we have number filter build query
09:48
and we have our access level ID, which we can use for filters.accesslevelID. And then lastly, if you did the module
09:56
that we can do filter.buildQueryModuleID, filters.moduleID, just like so. In order to apply these filters, let's go back up to our query then.
10:06
After our query method, let's do if filters. So if we have filters, let's go ahead and grab the query builder and apply in this apply filters,
10:14
passing that query in as well as our filters object. And we can go ahead and assert that is there since we have already checked it with our if conditional. That should now allow us to filter.
10:24
So jumping back into Hopscotch, let's add in a body to our request of type application JSON. I'm just gonna go ahead and scroll through our list here and see what our names are. So we have test lesson 01 edit.
10:34
We have my second lesson. We have my third lesson, my first lesson. So, okay, we can kind of get the pattern that's going on there. So let's go ahead and search for a name
10:43
that contains lesson. So we're just gonna do a basic stream search to match against lesson anywhere within the name. So if we send it off, we get back zero
10:52
because I think because I tacked an S on it, let's try searching for lesson instead. Yeah, there we go. Now we get four. All right, which does indeed match test lesson 01,
11:02
my first lesson, my second lesson, so on and so forth. If we search for second, we should just get one back, which we sure enough do. And if we switch this from a string to an object
11:12
with a search type of ends with, and set the text to 01, send that off,
11:20
we should get back, oh yeah, no, sorry. That ends with hyphen edit. So hyphen edit, send that off. Now we get back that one. There we go, okay. So that's all working A-okay.
11:30
We have a module ID of one for that one. So let's also make sure that if we tack on our module ID with a one, that that two will return back the exact same.
11:39
Let's now get rid of our name filter and see how many we have in module one. We have three, okay. If we send back an array of one and two for our module ID,
11:49
we get back four, cool. So everything there seems to be working A-okay for our filtering. What we will do next is add in the ability to filter by our publish at field.
11:58
For this, essentially what we're gonna allow them to do is filter between a start and end date. So we'll do a before and an after property. Then we'll check to see whether or not the publish at
12:08
within the lesson itself is before and after they provided values.