[{"data":1,"prerenderedAt":438},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-equipment-booking-manager":121,"100-apps-100-hours-equipment-booking-manager-next":169,"sales-reps":186},{"items":4},[5,29,49,69],{"id":6,"title":7,"url":8,"page":8,"children":9},"522e608a-77b0-4333-820d-d4f44be2ade1","Solutions",null,[10,15,20,25],{"id":11,"title":12,"url":8,"page":13},"fcafe85a-a798-4710-9e7a-776fe413aae5","Headless CMS",{"permalink":14},"/solutions/headless-cms",{"id":16,"title":17,"url":8,"page":18},"79972923-93cf-4777-9e32-5c9b0315fc10","Backend-as-a-Service",{"permalink":19},"/solutions/backend-as-a-service",{"id":21,"title":22,"url":8,"page":23},"0fa8d0c1-7b64-4f6f-939d-d7fdb99fc407","Product Information",{"permalink":24},"/solutions/product-information-management",{"id":26,"title":27,"url":28,"page":8},"63946d54-6052-4780-8ff4-91f5a9931dcc","100+ Things to Build","https://directus.io/blog/100-tools-apps-and-platforms-you-can-build-with-directus",{"id":30,"title":31,"url":8,"page":8,"children":32},"8ab4f9b1-f3e2-44d6-919b-011d91fe072f","Resources",[33,37,41,45],{"id":34,"title":35,"url":36,"page":8},"f951fb84-8777-4b84-9e91-996fe9d25483","Documentation","https://docs.directus.io",{"id":38,"title":39,"url":40,"page":8},"366febc7-a538-4c08-a326-e6204957f1e3","Guides","https://docs.directus.io/guides/",{"id":42,"title":43,"url":44,"page":8},"aeb9128e-1c5f-417f-863c-2449416433cd","Community","https://directus.chat",{"id":46,"title":47,"url":48,"page":8},"da1c2ed8-0a77-49b0-a903-49c56cb07de5","Release Notes","https://github.com/directus/directus/releases",{"id":50,"title":51,"url":8,"page":8,"children":52},"d61fae8c-7502-494a-822f-19ecff3d0256","Support",[53,57,61,65],{"id":54,"title":55,"url":56,"page":8},"8c43c781-7ebd-475f-a931-747e293c0a88","Issue Tracker","https://github.com/directus/directus/issues",{"id":58,"title":59,"url":60,"page":8},"d77bb78e-cf7b-4e01-932a-514414ba49d3","Feature Requests","https://github.com/directus/directus/discussions?discussions_q=is:open+sort:top",{"id":62,"title":63,"url":64,"page":8},"4346be2b-2c53-476e-b53b-becacec626a6","Community Chat","https://discord.com/channels/725371605378924594/741317677397704757",{"id":66,"title":67,"url":68,"page":8},"26c115d2-49f7-4edc-935e-d37d427fb89d","Cloud Dashboard","https://directus.cloud",{"id":70,"title":71,"url":8,"page":8,"children":72},"49141403-4f20-44ac-8453-25ace1265812","Organization",[73,78,84,88],{"id":74,"title":75,"url":76,"page":77},"1f36ea92-8a5e-47c8-914c-9822a8b9538a","About","/about",{"permalink":76},{"id":79,"title":80,"url":81,"page":82},"b84bf525-5471-4b14-a93c-225f6c386005","Careers","#",{"permalink":83},"/careers",{"id":85,"title":86,"url":87,"page":8},"86aabc3a-433d-434b-9efa-ad1d34be0a34","Brand Assets","https://drive.google.com/drive/folders/1lBOTba4RaA5ikqOn8Ewo4RYzD0XcymG9?usp=sharing",{"id":89,"title":90,"url":8,"page":91},"8d2fa1e3-198e-4405-81e1-2ceb858bc237","Contact",{"permalink":92},"/contact",{"items":94},[95,101,107,113],{"id":96,"title":97,"url":8,"page":98,"children":100},"8a1b7bfa-429d-4ffc-a650-2a5fdcf356da","Cloud Policies",{"permalink":99},"/cloud-policies",[],{"id":102,"title":103,"url":81,"page":104,"children":106},"bea848ef-828f-4306-8017-6b00ec5d4a0c","License",{"permalink":105},"/bsl",[],{"id":108,"title":109,"url":81,"page":110,"children":112},"4e914f47-4bee-42b7-b445-3119ee4196ef","Terms",{"permalink":111},"/terms",[],{"id":114,"title":115,"url":81,"page":116,"children":118},"ea69eda6-d317-4981-8421-fcabb1826bfd","Privacy",{"permalink":117},"/privacy",[],{"description":120},"\u003Cp>A composable backend to build your Headless CMS, BaaS, and more.&nbsp;\u003C/p>",{"id":122,"slug":123,"vimeo_id":124,"description":125,"tile":126,"length":127,"resources":8,"people":8,"episode_number":128,"published":129,"title":130,"video_transcript_html":131,"video_transcript_text":132,"content":8,"status":133,"episode_people":134,"recommendations":149,"season":150,"seo":166},"d072a935-906e-4208-a5dc-e9b117d0ab29","equipment-booking-manager","936331509","In a fast and furious hour, Bryant tackles building an equipment booking manager. Watch as he designs and implement a system to manage videography and photography gear, including features for checking items in and out, reserving equipment, and maintaining a comprehensive inventory dashboard.","f1a4f4a6-b473-4d02-a1c6-17474dad5c02",60,2,"2024-04-26","Mission: Equipment Booking Manager ","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 apps, 100 hours, where we build some of your favorite apps or publicly fail trying. Today we've got a really neat use case that we're gonna try to build, an equipment booking manager. This sounds really fancy. I've seen this topic come up a few times within our direct us community, and I I thought, hey, let's tackle it today.\u003C/p>\u003Cp>It's perfect. So in all of the research I did, which is basically about 5 minutes before I pressed record on this, I found a comparable solution. It's called Checkroom, c h e q, Room. Looks like a pretty neat solution, but basically it's an internal app that allows you to manage all of your equipment. So equipment, in this case, it looks like they're targeting videographers, photography, you know, maybe studios, anybody who's got expensive camera equipment or video cameras, etcetera.\u003C/p>\u003Cp>But basically, you know, you can check these items in and out, reserve them for a time slot, manages all your inventory, what's the the status of all those, gives you a nice friendly dashboard. So that's what we're gonna be building today. If this is your first time watching 100 Apps 100 Hours, there are 2 rules. Number 1, we have 60 minutes to plan and build, so we're on a bit of a time crunch. There's no more, no less.\u003C/p>\u003Cp>And rule number 2 is, kind of, the anti rule. Use whatever we have at our disposal. So with that let's build our equipment booking manager. I'm gonna start the clock here and away we go. So I'm inside, Figma here or FigJam.\u003C/p>\u003Cp>This is how I like to plan before I build, just kind of quickly whiteboard things. So let's go in and just flesh out the the actual functionality that we're looking for out of this specific app. Right? We wanna track inventory of all of our different items, of all equipment and items. This could be, like, mobile phones or Android devices if we're doing web development for testing, camera equipment, printers, what whatever.\u003C/p>\u003Cp>We wanna be able to reserve equipment equipment on a schedule. We want to be able to track, details about the equipment. Let's say user can reserve the equipment on a schedule. We're gonna be able to track details of track equipment status. I mean, that's that's pretty much the the bare bones for this.\u003C/p>\u003Cp>You know, when are these things scheduled? Is it available for checkout? You know, maybe we wanna account for things like maintenance. Put, like, a big question mark there. Yeah.\u003C/p>\u003Cp>Okay. That that seems pretty good. Let's let's dive in and actually start planning what our data model may look like for something like this. So I'm just going to drag a nice square over here. Let's change this to red to kind of match.\u003C/p>\u003Cp>Alright. So thinking about this and and just talking it through out loud, obviously we have our inventory. This could be called items, could be called equipment. Let's let's go with equipment. That that seems like a a good compromise there.\u003C/p>\u003Cp>Items has a, kind of, a specific meaning inside Directus, which is just the actual individual pieces of content or data. So let's go with equipment, and then we're going to have, users. Alright. So that's gonna come actually from a Directus underscore user collection that Directus is gonna give us out of the box, which again is really nice. We got that authentication tied to that as well.\u003C/p>\u003Cp>And then we're gonna have, what, bookings for each of the this piece of equipment. Right? So we have a relationship here between equipment and bookings. The user creates a booking for a specific piece of equipment. You know, we have a check-in, check out type of functionality.\u003C/p>\u003Cp>It's great. Yeah. And this could be, you know, one of the other things that I saw here on the check room website, where was this? Inventory. One inventory to rule them all.\u003C/p>\u003Cp>You could potentially bundle these things up. Capture the flag. So, you know, maybe some type of checklist. Right? Let's go through and and just start fleshing this out.\u003C/p>\u003Cp>Right? We're about 4 minutes in. We need to actually start building something. So I'm gonna pull up my instance of Directus, so this side by side. We'll zoom in just a little bit.\u003C/p>\u003Cp>And this is just a blank direct assistance that I've spun up here locally, if I can remember the actual password. I think it's real secure. It's probably just password. Great. So we've got a totally blank instance of Directus.\u003C/p>\u003Cp>Let's start building something. Let's start with equipment. Right. We're going to just call this table equipment. Usually I've got a plural at the end of a lot of my tables, but equipments doesn't make a ton of sense to me.\u003C/p>\u003Cp>So, what are we going to have on this? We're going to have a status, like the default status field here. These just make it easy to add some of the most common functionality you'll have. It uses, like, a draft, published, archived. So I'm just gonna leave that blank for now.\u003C/p>\u003Cp>The sort field, do we really need a a sort for our equipment? Probably not. It might be good to track when it was created, so when it was added to the database, who it was created by, and the last time that information was updated. So we'll just roll with that. And let's give this, a name, right?\u003C/p>\u003Cp>This is the name of this piece of equipment, it's just gonna be a string. Really simple. No need to get fancy with it. Let's require a name for it. And next, let's probably add a a serial number.\u003C/p>\u003Cp>We need a unique identifier for this. You know, maybe we want, we end up printing these on labels or QR codes. Serial number would be a good way to track that. And, you know, we can choose to require that as a value, you know, maybe we don't necessarily need to, but we'll add that. What else do we wanna add here?\u003C/p>\u003Cp>We want to add a description. So maybe that's gonna be a wysiwyg editor. You know, this could be a HTML description, so we'll go with that. The type is gonna be text. I could customize my toolbar if I want to limit the options that we've got, but we'll just roll with the the default selections for now.\u003C/p>\u003Cp>We're probably going to have, what, some images of this or at least one image. You know, maybe we've got a gallery, but let's just call this the image. That's gonna be a relational field, Just a mini to 1 to our directus files collection. Make sure we can add an image for it. What else am I forgetting?\u003C/p>\u003Cp>We've got a status field. So, let's use a drop down for this. We'll call it status. The type, we're just gonna store that as a string. And for our choices, right, when we think through the choices, the equipment is either currently available, it is unavailable, you know, maybe it could be like, hey, it's getting serviced or it's in maintenance.\u003C/p>\u003Cp>I'm not sure if that's worthwhile to track or not. Alright. So let's just add a new one. We're gonna call it available. For the value, we can use lower case.\u003C/p>\u003Cp>Let's give a, maybe, a check mark. Hey. This is available. We'll make it green. Then we'll have unavailable.\u003C/p>\u003Cp>I'm sure there's probably a better naming convention here, but this is what we'll roll with. Maybe like a x. Okay. This is unavailable. And, yeah, maybe we do it like a status for maintenance just to differentiate.\u003C/p>\u003Cp>Maintenance. Wrench, maybe. Let's look for a wrench repair. There we go. We got a toolbox.\u003C/p>\u003Cp>That'll work. Okay. And I think yellow or orange is a good color for that. Great. So we've got available, meaning it's currently in inventory.\u003C/p>\u003Cp>Unavailable means it's out, Not in inventory. And then we have maintenance. Don't need to allow any other values. Looks great. Trying to think of what else we actually need for this data model.\u003C/p>\u003Cp>You know, we may have, like, specific details that we wanted to track, those are escaping me at this particular moment. So we've got equipment. Let's go in and add a few pieces of equipment. You know, we'll just start out and say, this is available. We have a, just looking at my desk here, I have an iPad Pro that is BG iPad Pro.\u003C/p>\u003Cp>Just create a serial number. The most amazing iPad you've ever seen. And we'll just go to Google, and it's still an image. Right? IPad Pro.\u003C/p>\u003Cp>Perfect. Where are you? Apple's gonna sue me, I'm sure. Please don't. Alright.\u003C/p>\u003Cp>Swipe an image here. That's great. We've got an iPad Pro. I may want to adjust my view just a little bit. I can't I don't see those nice images.\u003C/p>\u003Cp>I could go in and add my thumbnail for this. Looks great. Cool. We can make this a larger view. We can make it comfortable, and I can see a little more of that.\u003C/p>\u003Cp>Or I could even go straight to cards view here and get, just kind of like a gallery of where we're at. So the subtitle here could be the status, it's available. Great. We could add one more piece of equipment. You know, maybe I've got a, show my age here.\u003C/p>\u003Cp>The Sony a 64100. Sony a 64100. We could call this 1. Great. So we'll find a image of the Sony.\u003C/p>\u003Cp>I think it's the a 64100. Yep. That's the one. Copy image address. We'll just pull that in.\u003C/p>\u003Cp>Great. And, yeah, maybe I've got something like 2 of these cameras. How can I, like, achieve that really quickly? How can I duplicate these inside Directus? Well, what I could do is just go ahead and change the name a little bit, change the serial number, and I could go into the 3 dots up here at the top and click save as copy.\u003C/p>\u003Cp>So now I've got 2 of those specific cameras, maybe one's available, one's not, one's currently checked out, the other isn't. Cool. Alright. So we've got some equipment. What is the next thing that we're going to build?\u003C/p>\u003Cp>Let's take a look at bookings. Right? So how is this going to work? We're going to maintain a schedule of bookings. We're probably going to use our calendar view inside Directus for this, and then we'll be able to check-in and out this equipment, or actually reserve this equipment.\u003C/p>\u003Cp>The check-in, check out part may be, something else a little different. So let's just call this table bookings, makes sense. For the primary key we'll just use an ID. I'll use generated UUID for that. For the status, right, Let's leave status off here.\u003C/p>\u003Cp>We're just gonna check the box for all of the date created, date updated, all these system fields that auto populate when certain actions happen. And now we've got a booking, right, so when is the we we need the times for the booking. So let's do, like, a scheduled from. We use a time stamp format just so we get the time zone value. K.\u003C/p>\u003Cp>Let's make that high second half width. We'll do schedule 2 for that as well. Schedule from, schedule 2. Do we have notes? You know, I'm thinking, like, hey, we may have to have some type of notes.\u003C/p>\u003Cp>We'll use a text area for that. I don't need these to be formatted in a WYSIWYG or markdown. You know, could be potentially nice if you need that level of functionality. Alright. So we've got a booking, we've got schedule from, schedule to.\u003C/p>\u003Cp>We need, obviously, a piece of equipment tied to this booking. So now we're gonna start reaching for our relationships inside Directus, because each booking could only have a single piece of equipment, or could it? You know, we could potentially book a 3 or 4 pieces of equipment, but maybe those would be individual bookings as well. You know, it gets more complicated when you have a kit. Maybe we'll try to explore that.\u003C/p>\u003Cp>But for now, let's just keep it simple. We'll do a mini to 1. Or would it be, yeah, let's do mini to 1 for now. The related collection will be equipment. And we'll go into the relationship here.\u003C/p>\u003Cp>I can see what this setup looks like, but I can go in and also add that inverse relationship. So, I can add equipment to my bookings or add bookings inside my equipment. So I if I pull up a piece of equipment, I can see all the bookings right inside that item detail page that that that particular piece of equipment has had. Alright. Long winded explanation there, Brian.\u003C/p>\u003Cp>Alright. So we've got a schedule from, schedule 2, notes, equipment. Who is this assigned to, or who's pulling this equipment? I again, naming things is is probably the hardest challenge in development. Let's just call this assigned to or bookie.\u003C/p>\u003Cp>Call here bookie. Let's call it assign to. That's fine. So now I can't see my users collection here. Directus users is actually collection.\u003C/p>\u003Cp>So we're just gonna go in and expand that system drop down, pick Directus users. I could control the display template here, so what actually shows up. You know, maybe I want to show the image of the user, the first name, the last name. Great. And save it.\u003C/p>\u003Cp>Alright. So we've got the schedule from, schedule to. We probably need a status on this as well. Right? So who is this booking for?\u003C/p>\u003Cp>Alright. The status, when we think about that, again, let's use a drop down just because we get, like, a rich look at it, now that we have the ability to add icons and colors. But when I think about this, let's say, what's the initial state on a booking? Would be new or maybe unconfirmed is what I'm thinking here. So a new booking comes in, somebody has to verify that that booking is okay, or maybe we could do that automatically via flow.\u003C/p>\u003Cp>But we'll call it unconfirmed. And let's just see if we've got, like, a there we go. We've got a new flow. Fiber new. That's an interesting name for that particular icon.\u003C/p>\u003Cp>What else do we have? Scheduled. Scheduled. K. Time.\u003C/p>\u003Cp>See what we got. Okay. Let's just do, like, blue. That's kind of like a neutral color as far as what's going on. You know, maybe in progress is the next one.\u003C/p>\u003Cp>We'll just create some text and value for that. Settings. Maybe, like, a gear, progress bar, meter, bar. Yeah. There we go.\u003C/p>\u003Cp>That'll work. Alright. We'll mark that as I guess, as green in the state of a booking. Maybe it's red. In progress.\u003C/p>\u003Cp>Maybe completed is the state. Alright. So this is a completed booking. Check mark. I think that's a that'll be a nice icon for this.\u003C/p>\u003Cp>Alright. Completed booking. We'll go to check mark. We'll make that green. Great.\u003C/p>\u003Cp>Okay. So now we've got our status field filled out. We'll hit save, and now we can start looking at at how this is gonna come together. So we've got equipment, we've got our bookings, you know, maybe we look at this on a calendar view. Our layout options here we're gonna adjust this view.\u003C/p>\u003Cp>Each one of these layouts inside Directus has, separate configuration options that are dependent on the specific view. So here maybe we display, let's see. We're gonna display the equipment name. That makes sense to me. And then let's also maybe display the user as well, so who this is assigned to.\u003C/p>\u003Cp>Assign to first name. Assign to so we'll reach into that related collection, and I can display those related values. I thought I had added that. Assign to last name. Oh, I'm just not seeing it there.\u003C/p>\u003Cp>First name, assign to first name. I added it, I just didn't see it. Last name. Okay. Alright.\u003C/p>\u003Cp>The start date field is gonna be scheduled from, scheduled to. 1st day of the week is Sunday, and then we're good. Right? So let's go in and create our first booking. We'll just do this manually.\u003C/p>\u003Cp>And I can actually do default values as well, one of the handy things about when when you're setting up your data model inside Directus. If I go into my status, I've got unconfirmed as a value. If I go to my schema, I can just set the default value to be unconfirmed, and now whenever I create a new booking, the default is unconfirmed. Alright. So we're going to pick a piece of equipment.\u003C/p>\u003Cp>Let's say we want this Sony a 64100. That's gonna be assigned to me. I wanna check this out, for 2 days at near the end of this month and using for a photo shoot. Great. So now I could see that booking on my calendar, and presumably as these bookings come in, you know, I could go in and and see this schedule.\u003C/p>\u003Cp>Right? This is really a bare bones functionality. You know, I I could give access to our team to this and set up some separate permissions, but now, I let's maybe add some automation to manage this booking process. Right? It seems kind of hey, the flow doesn't seem quite right to me.\u003C/p>\u003Cp>So we wouldn't have somebody go in and manually adjust the status for their booking. You know, maybe we can take advantage of Directus flows and and build something to to, account for this. Right? So we'll go inside flows, and let's just let's think about how to manage the bookings. Each one of these specific bookings, has some actions that you're taking against it.\u003C/p>\u003Cp>So to me, if you're used to building, like, inventory systems, you probably got some type of transaction against that inventory of, hey, this was checked in, this was checked out, it was reserved, or it the on hand quantity was reduced. So maybe we do something like that where we track the individual transactions against these bookings of when it was checked out, when it was checked in. Let's do that. So we'll do bookings underscore transactions, and I'll just use UUID again. Just kind of the standard for me.\u003C/p>\u003Cp>Maybe the created on do we really need, alright. We're not gonna update these transactions. So, yeah, if we're created on, I'm just gonna change this to timestamp because that is the timestamp this particular transaction occurred. We'll use created by just so we have the user created that particular transaction, so if that was me checking myself in or somebody else checking in that booking. Alright.\u003C/p>\u003Cp>So at that point we also are gonna have a relationship to our bookings. So let's go ahead and set that up. Here it's going to be a mini to one relationship from booking transactions to bookings, because, a a transaction only has one booking, but a booking could have many different transactions attached to it. So we'll call this the booking, and our related collection is gonna be bookings. And for the display template, let's say that is the equipment name, space, date, schedule from.\u003C/p>\u003Cp>And we do schedule to. And if I wanted to, I can even go in and add the the specific user. So we'll do first name, assign to last name. Perfect. Alright.\u003C/p>\u003Cp>Cool. So now we've got a booking, and I forgot to create the inverse relationship. There's when you're creating that mini to 1, there's a second tab for that, but no fear. We can just go into our bookings and and we can add that from here. So we'll do the one to many field.\u003C/p>\u003Cp>So this is the inverse relationship. We'll call this our transactions. And let's do booking transactions and the foreign key we'll already have, which is the booking. So this is the foreign key for bookings inside transactions. We'll just save that, maybe show a link to it and hit save.\u003C/p>\u003Cp>Alright. So now if we take a look at one of our bookings we can see this transaction table. Alright. The other thing that we're probably gonna need on our booking transactions, and honestly, like, this may be hidden behind the scenes, and I'm just gonna go in and add some icons to this before it drives me crazy. Recovering designer folks.\u003C/p>\u003Cp>Bear with me. Camera. There we go. Looks great. Alright.\u003C/p>\u003Cp>So on our booking transactions, and I may even just nest that, we're gonna need a action type. Alright. So it's a check-in, check out. You know, move to maintenance, maybe, as well. For this, let's try something like radio buttons.\u003C/p>\u003Cp>So this will be the action. The choices that we'll have will be check-in, the value here, check underscore in, Check out. Check out. Maintenance. Move to maintenance.\u003C/p>\u003Cp>Request maintenance. Let's just call it maintenance. I'm getting too fancy with it. Taking too much time when I'm actually maintenance. I'm not even sure if that's the correct spelling.\u003C/p>\u003Cp>That's what we're rolling with. Alright. So now, what have we done? We've got some equipment, we've got bookings we can use to manage that, and then we can attach transactions to those. Right?\u003C/p>\u003Cp>So as far as our underlying data model, this looks pretty cool. Let's work on our our flow. Right? How can we make this easier? So I can imagine, like, if I'm looking up bookings, you know, maybe I want to bookmark this.\u003C/p>\u003Cp>This is the calendar view. So we'll add a calendar icon for that as well. Alright. So when I save a bookmark, that gives me the calendar here. And then maybe I can go in and maybe we just want, like, a list of all the bookings as well.\u003C/p>\u003Cp>This could be a new potential bookmark. Let's go in. We don't wanna update that original bookmark. I go back to bookings. We'll just create a bookmark for this.\u003C/p>\u003Cp>We'll call this list. This is a list of bookings. Great. So one of the other nice things about creating these bookmarks inside Directus is if you have certain filters applied or, you know, even a search query, you could save that search query and create these really nice slices of your data. But as far as my list view, you know, maybe I want to clean this up a little bit.\u003C/p>\u003Cp>My assign to field looks kind of boring, my equipment is not showing. Let's just make this look a little nicer. So I'm going to go back inside my data model and where I have equipment I'm gonna look at my interface and display. So here we have our interface, which is gonna be how it looks inside the detail view. The display is gonna be how it presents on the layout.\u003C/p>\u003Cp>So I could go in and maybe we want to show the, image, And I can expand that a little bit just to get a a thumbnail. And we'll show the name of this, and we'll do the same here. One of the little tricks I can use here is just drop this down, copy raw value, we can also paste it here and get access to the same thing. Great. As far as the assign to, maybe we want to do the same thing.\u003C/p>\u003Cp>Copy for the display. Because this is a user, I could also just show the direct us user. And maybe we wanna show the user in a circle instead of a square. Totally up to you. So there we go.\u003C/p>\u003Cp>This looks a little better. We'll probably have the schedule 2 on there as well. And maybe I wanted to get a little fancy with some conditional formatting for the status. Again, pretty easy to do. We'll just go to status, we'll do display, a couple of different ways I could display this.\u003C/p>\u003Cp>Let's just go with a label, and I'm just going to copy the raw value from these choices. I'm gonna paste it here and then, you know, I could adjust the actual colors, like background and foreground if I need to. But let's just see how this presents. Yeah. That looks that looks definitely a lot better, and nice to have like an icon as well.\u003C/p>\u003Cp>Alright. So now we've cleaned this up a bit. Let's build a flow to check-in and out. So what we'll do, we'll go into our flows. Directus Flows is a great workflow automation tool.\u003C/p>\u003Cp>So I can set up, flows to run on any number of things. This one we're going to use the manual trigger for, which is one that I use often. I really like this. So let's call this the check-in. And do we have, like, an in there's an in out.\u003C/p>\u003Cp>Maybe that's, like, a in out burger, maybe? I don't know. Let's, let's do, like, an arrow forward for check-in. That looks good. And then when we do a checkout flow we can move backwards.\u003C/p>\u003Cp>So I move on to the trigger setup. Again, you've got lots of options here. You've got event hook, you could, so basically when an event happens inside Directus we're going to start this specific flow. I could trigger on webhook requests from other third party services, a schedule, or in this case we're going to run this manually. Alright.\u003C/p>\u003Cp>So this is going to be on our bookings collection. Right? The location is going to be where this displays. So if I want to show it on the collection, like the view, the layout page, I can do collection page only or I can do item page only to show it with in a specific booking. Or if I do both, I can, get get the option to pick from the collection page or I could do it from the detail page as well.\u003C/p>\u003Cp>So great. We'll go in and let's just require confirmation on this specific booking and, you know, let's add some inputs for this. Let's add some notes. Any notes about this booking? So I'm trying to think of what this would be.\u003C/p>\u003Cp>You know, maybe, hey, they showed up late or, you know, something like that. But we're already gonna be picking a booking so we know who this is. Let's give this a shot. And the first time I'm, the first thing I do whenever I'm building flows is basically start with my trigger, and then I'm going to go in and actually trigger my flow. So over here on the right, I can see my flows, I can select this, right?\u003C/p>\u003Cp>If I unselect, I won't be able to check this in. But we'll just say, hey, I'm going to check this in and here's some notes about this. Great. Alright. So now if I go back into my Flows, we'll see a couple things.\u003C/p>\u003Cp>Right? We've got our logs that we're gonna take a look at. We've got our payload. And if I look at the body, we've got the notes, we've got our collection, and we've got our keys. So this is the actual ID of the booking that we selected.\u003C/p>\u003Cp>Great. So when I think through this flow, right, what are we going to do? There's a a couple things we're probably going to want to achieve with this flow. If we go back into our data model just thinking out loud here. Alright.\u003C/p>\u003Cp>We're going to this is getting in the way. Let's just do text. We're going to want to create a booking transaction. We're probably going to update the status, update the status of the booking, and then we're going to update the status of the underlying equipment. Right?\u003C/p>\u003Cp>So that's gonna move from available to unavailable. Alright. We can achieve all of those inside of Flow. Let's get to it. Alright.\u003C/p>\u003Cp>So we are going to create our first operation. Let's call this, create booking, create transaction. Okay. We're gonna create data. So we're going to create an item in the database that's going to be our booking transactions.\u003C/p>\u003Cp>We can leave the permission set to from trigger because we're going to run this flow inside Directus. If this was triggered on a webhook event or something like that, you would probably want to use full access because you're not going to provide that accountability, that direct us user to actually run this flow. But we'll go into our payload and listening through this, we've got a booking. So let's just throw that out there. And then that is going to be coming from our trigger.\u003C/p>\u003Cp>So whenever I want to access data from that trigger event, I have to use this special syntax and just add a dollar sign in front of it. That is the only time I need to use that to access data from this flow. So only in the trigger. And let me just take a look. It could be handy if you take and copy this, into Versus Code or your editor just to make it easier to remember what it was as you're building out these flows.\u003C/p>\u003Cp>So we've got the trigger dot body dot keys. This is an array. We're gonna have to pick off the first value of that array. Alright. So let's run this again.\u003C/p>\u003Cp>Create transaction, and we'll do booking transactions, and paste this in. Trigger dot body dot keys, and we're gonna grab the first item from that array. And again, we're using the mustache syntax here. We can close this, and we're also going to have an action. Alright.\u003C/p>\u003Cp>So if you remember, that action is going to be check-in. And is that all we need? I think that's all we need. Great. Alright.\u003C/p>\u003Cp>So the next thing that we're gonna do, we want to update the status of that booking. So this is a check-in. We are going to update the status of that booking to what? So if we look at our booking, we've got, we're gonna change that to in progress. Right?\u003C/p>\u003Cp>Great. So we'll do update our booking, and that's gonna be update data. So we'll just call that update booking. That's gonna be our bookings collection. Again, we'll use the trigger permissions.\u003C/p>\u003Cp>Okay. So 2 ways I could go about grabbing the ID of the item that we're gonna update. I could use the IDs field to specify the specific IDs, or I could run a query for this. I'm going to lean this way just because we already know what that value looks like. So it's going to be dollar sign trigger dot body dot keys, first item of that array, and I'm going to press enter to make sure we store that.\u003C/p>\u003Cp>I can also go in and edit the raw value here just to see what that looks like. So again, if I'm using this mustache syntax, it's going to dynamically populate that data for me. We do not want to omit events for this. We don't want to trigger any type of, like, infinite loop using that, so always be careful when you're using that. And for our payload here, we're just going to update the status of this booking.\u003C/p>\u003Cp>The status is going to be in underscore progress. And I think I think that's all we're really looking for here, right. Now, we need to update the underlying piece of equipment as well. And if you take a look at our log, though, right, we are not getting that information here. We're only getting the key for the booking.\u003C/p>\u003Cp>So in that case, before we can update the actual piece of equipment this is attached to, we probably need to get the booking so we can find that, equipment that's attached to it. So let's just create a new operation. We're going to call it find booking. We're going to use the read data operation. We're going to find bookings collection.\u003C/p>\u003Cp>And then, again, we're just gonna do the same one, trigger.body.keys, save that. And for our query here, I could pass any of the the standard query parameters that are available. If you're not familiar with those, check out our docs. But what I'm gonna do, I'm gonna pass a fields query, or fields property, and this is gonna be an array. I'm gonna get all the root level fields, that's what this asterisk will do, And then I'm also going to get all of the equipment fields as well.\u003C/p>\u003Cp>So I am drilling through my relationships here. One of my favorite features of Directus is the ability to get and expand those relationships in a single API call. So assuming that goes well, then we are going to update that piece of equipment. Right. Before we do that, let's just stop and go test this out.\u003C/p>\u003Cp>Alright. So we are gonna go here. I'm gonna hit check-in. Here's some notes, and, you know, I could also save these on that transaction. We'll maybe do that in a moment.\u003C/p>\u003Cp>But we can see here the change. We've got a status of in progress. We've got a new transaction here. We can see the time stamp that was created. It was a check-in action.\u003C/p>\u003Cp>Let's go into that booking transactions. We'll add that notes field as well, just so we can store those notes. Great. And let's run into our flows and just see the log. Right?\u003C/p>\u003Cp>What did we get for the booking? Are we getting the data that we want? We can see the equipment dot ID. That's what we wanna update, and we're gonna change that status to available or unavailable because it it's unavailable. Alright.\u003C/p>\u003Cp>So this last link in the chain here will go in and update equipment. And You can see Directus will automatically generate the key for me. If you want to change that, feel free to. Not a big deal. But these keys are how you access the data in subsequent operations.\u003C/p>\u003Cp>Right. So as you can see in just a moment, we're going to go into choose our equipment collection. We'll do from trigger. But as far as the ID's that we're going to update, we're going to use, find booking_orfindbooking.equipment.id. So that is how we're gonna access this.\u003C/p>\u003Cp>And for our payload, we're just gonna change the status. Unavailable. Okay. Great. Cool.\u003C/p>\u003Cp>Alright. So I'm just gonna go in and manually change this. This is, let's call it a scheduled booking, for I'm gonna undo these transactions, unhide those just because I want to delete this one. Alright. So we're starting fresh.\u003C/p>\u003Cp>Everything's available. We've got a booking for our specific camera. I'm gonna go in, select this. I can also go into the item detail page here and do the same thing. And, oh, also forgot to update our notes.\u003C/p>\u003Cp>Right? Let's go ahead and do that as well. The perks of building on the fly. How are we looking time wise? Yeah, good.\u003C/p>\u003Cp>Pretty pretty solid time wise. Alright. So for our transactions here, we're gonna pass the notes, and that will be trigger, dollar sign trigger, dot body dot notes. Okay. Just some standard JSON format there.\u003C/p>\u003Cp>Great. Alright. Moment of truth. Let's see if this is actually gonna work. We'll go in, run check-in, our check-in flow.\u003C/p>\u003Cp>Bryant was late showing up to pick up this camera. Great. We'll hit run flow. Now we can see this is an in progress booking. If we look at our equipment, it shows as unavailable.\u003C/p>\u003Cp>You know, maybe we could potentially, show that a little nicer, maybe with a badge or something. And what else did we update? Right? We've got a transaction, we're storing our note. If we go into that detailed booking, we could see the transaction here.\u003C/p>\u003Cp>Maybe we want to clean this up a little bit for our users because we don't want them to see that UUID. Alright, so again, optimizing for our users. Let's go in and we'll go to the transactions. What do we want to do? We want to do show the timestamp, what the action was, and maybe if there's any notes.\u003C/p>\u003Cp>Oops. Let's do notes. See that? Cool. Okay.\u003C/p>\u003Cp>So now I can see this, 1 minute ago it was a check-in event, Bryant was late showing up to pick up this camera. And I can even go a step further with this, or I should be able to, in the action here for my display, I could show a formatted value of this. So we can show a border, and if this is a check-in, you know, maybe we wanna show, what did we use for this? Maybe, yeah, green will be fine. We could show a checkbox.\u003C/p>\u003Cp>Or, actually, I think we were using, like, a arrow. Right? Arrow. Check-in. And check out of it.\u003C/p>\u003Cp>So if the value equals that, we can actually, I'm getting these wrong, aren't I? We'd probably be checking out the equipment, not checking in. So maybe I need to go in and fix that as well. Arrow. Left.\u003C/p>\u003Cp>Right. There we go. Check-in. Oh, let's use left for that one. Great.\u003C/p>\u003Cp>Okay. Alright. Cool. So now we've got our actions. If I take a look at our transactions, we could see we got a badge for that, and I would probably actually flop these.\u003C/p>\u003Cp>Right? That would be this would be like a, yeah, checkout and not check-in. So we can change this. Right? This is a checkout event.\u003C/p>\u003Cp>And is there anything else we need to? I don't think we need to change any of these other ones. Alright. Cool. So let me go in and just fix this again.\u003C/p>\u003Cp>Alright. I'll delete this record so we don't have any more booking transactions there. Reset this guy to scheduled, and let's try it again. Check out. Late again.\u003C/p>\u003Cp>Run one flow on the selected item in progress. Now we got our checkout. Great. So I could do the same operation just in reverse for, like, a a check-in. So when we are checking this equipment back in, pretty easy to do.\u003C/p>\u003Cp>What other functionality do we need out of this? You know, is there an opportunity to take this one step further? We still got, about 15 minutes here. So what if we were to send some notifications on check-in or, like, if we got a new booking, we want to send some type of alert or or notification to someone. So maybe we reach into our flows for that.\u003C/p>\u003Cp>We'll go in and, you know, we could do a maybe like a a check-in flow as well. Right? So we do all this over again. We are going to do a check-in flow. Isn't that exciting?\u003C/p>\u003Cp>Let's do a notification. Right? New booking notification. Alright. So whenever a new booking occurs, we want to trigger a notification.\u003C/p>\u003Cp>That is going to be using our event hook, and you get 2 types of those event hooks inside Directus, whether that's a filter, which is a blocking. So this is great if I want to run some type of logic and then potentially either adjust the payload of the item that we're saving or, you know, updating, deleting, etcetera, or, you know, if I want to actually cancel that event pending some logic. In this case, we're going to do action non blocking. We don't want to block anything from occurring. Just after a new booking occurs, we are going to run a notification to a user.\u003C/p>\u003Cp>Right? So our scope here is items dot create. That's gonna be on a new booking And this also could be we could condition this down if we wanted to, where we only trigger on unconfirmed bookings. Unconfirmed. Alright.\u003C/p>\u003Cp>So the condition gives us, if, then, or if, else logic. So I can separate my flow into 2 separate paths, right. If the condition passes, we go this way and we run some other operations. If it fails, we do something else. So here's the syntax for this.\u003C/p>\u003Cp>We're just gonna use trigger. I'm breaking my own rules here. Let's just save this as is, and let's actually go create a new booking. And let's do it for the iPad Pro. We'll do the rest of this week.\u003C/p>\u003Cp>That's gonna be assigned to me. And here's some notes. Alright. So we created a new booking. I'm gonna get, a a payload here that I can take a look at, right.\u003C/p>\u003Cp>So the the payload is a little different than what we get from when we manually trigger these. But we can see we've got the equipment, we've got schedule from, assigned to. Great. So only on unconfirmed, what are we going to trigger this on? Basically, the unconfirmed.\u003C/p>\u003Cp>I really don't even think we need a condition here because the default value is unconfirmed. So, yeah, we could we could potentially do something like this. Like status is I think it's not equal to unconfirmed. Let's see if that runs. And then we're going to notify a certain member of the team.\u003C/p>\u003Cp>Right? Let's send a notify Matt. I'm going to pick on Matt on my team. We're going to send Matt a notification. And to do that, we have to have a UUID for that user.\u003C/p>\u003Cp>And I don't actually have a user in this account for Matt. Maybe we'll just use myself. Or we'll just create 1. Matt Minor. Great.\u003C/p>\u003Cp>Alright. We'll pick up the UUID from that specific user. And, you know, part of this flow could be like picking that up dynamically, depending on how you decide the logic of who to notify here. But I could just hard code that value in, hit save or hit enter to record that, and then we're going to add a notification. Right.\u003C/p>\u003Cp>New booking. Please review this booking and confirm. And as far as the collection here, the item, we are going to do what? Let's look at our log here. We got the payload.\u003C/p>\u003Cp>We're gonna do the key. So that's the created booking. And I can link that notification to that specific item. So it looks something like this, where we have trigger dot key. And that should do the trick.\u003C/p>\u003Cp>Alright. One of the other things that we may want to do, let's just check time really quick, we've got about 10 minutes. One of the other things that we may want to do is lock down some of these fields when you're actually editing this data. Right? When I am looking at a booking, I may not want anybody to be able to update the status without going through a specific, like, check-in or check out flow that handles all the logic that we want.\u003C/p>\u003Cp>Or likewise, you know, I I don't want any of my non admin users to be able to change the assignment for this. So there's 2 ways I could do this. If I go through the actual data model, you know, if if the booking status is controlled almost exclusively through Flows, I could go in and I could disable editing for this value. That'll be across the board, basically. Inside the UI nobody can update this value.\u003C/p>\u003Cp>So this is what that looks like. Now if I go into status, it just shows me the read only field, and if I wanted to change the status I would have one of my flows or update this via the API. Right. Here's my notes. I run this flow.\u003C/p>\u003Cp>My status is changed to in progress, and, potentially, I've got some type of, operation here to move this, like my check-in process. Right. But the other ways I could do this are via the Directus access control. So I could go in and create a, you know, let's call it a team member role. They will have app access, they won't have admin access, so they can't adjust the flows, they can't adjust our data model, but we do want them to be able to do certain things, like, view all of our bookings.\u003C/p>\u003Cp>Right. So I can give all access to start with, maybe we start whittling down some of these permissions. There's no need to share it. I don't want them to be able to delete bookings, delete equipment. I do want them to be able to update these fields.\u003C/p>\u003Cp>It could potentially mess with our flows as well, but let's address it. Right. So we're going to use the custom permissions for our bookings, the field permissions oh, I've gotta go in and do no access. Alright. So here's what we could do.\u003C/p>\u003Cp>We want to allow them to update only certain fields. Right. Maybe they can change the equipment in case we need to swap that. They can't change the status. They can change they can't change the assign to.\u003C/p>\u003Cp>They can't adjust the transactions, but they can see them. Sounds great. I can even configure field validation rules, or presets. Like, hey, here's the the default values for those. The only other thing that I'm concerned with here is because we're locking down that status, inside our flow for checkout we're actually using the permissions from the trigger here.\u003C/p>\u003Cp>So we'll just do full access, which basically gives unlimited access to, update these things. Great. Cool. And then, you know, if we go back into access control or team member, maybe we don't want them to update the equipment status either. Alright.\u003C/p>\u003Cp>So we'll give access field permissions. These are the fields that this role can update. They can update the name, serial number, description, image. I can't update the bookings. Status, no.\u003C/p>\u003Cp>Okay. Obviously they can update these. Cool. Alright. So we'll save this And now let's open up an incognito window.\u003C/p>\u003Cp>But first I need to give mister Matt Minor some permissions, or just a login. Matt@example.com. Give it a password. Great. Open up this incognito window.\u003C/p>\u003Cp>Go local. And we'll do matt@example.com. Password. Password. Did I did I get the password wrong?\u003C/p>\u003Cp>Did I even give them a role? So a couple things. I need to give Matt that team member role so he can have app access. And, you know, maybe I typed the password wrong. Password.\u003C/p>\u003Cp>Let's just copy that and make sure. Hit save. Okay. There we go. Alright.\u003C/p>\u003Cp>So now this is what Matt would see when he's logged in. You know, immediately you'd see he doesn't have access to the admin interface. But if he goes within the bookings, he can't update the status, he can't change the assignments, he can't do any of this stuff. But what he can do is run this flow. Here's some notes.\u003C/p>\u003Cp>This is not gonna really change any of this. Let's just zoom back out. Too zoomed in. Alright. So for the iPad Pro, I'm just gonna change this did I lock that down?\u003C/p>\u003Cp>I totally locked that down, didn't I? Let's go here. We'll change status. Go to the disable editing value. I'm just going to remove that just to demonstrate this capability.\u003C/p>\u003Cp>Alright. So now we go to iPad Pro. You can see over here I'm logged in as admin on the right hand side over here. I can change this status to be whatever I want. Hey, this is scheduled.\u003C/p>\u003Cp>I've got full permissions. Matt, when he's logged in, he can't edit any of the specific values. Right? But what he can do, he can still run these flows. And here's my notes about this booking.\u003C/p>\u003Cp>Right. We run the flow. It still updates all of our data, but in a restricted manner. You know, it only runs the logic that we want to run, so we can force Matt down a particular path that we want him to go. Alright.\u003C/p>\u003Cp>So as far as equipment manager, I'm calling this a win. We have got our inventory. Let's look at our list. Right? Track inventory of all of our equipment.\u003C/p>\u003Cp>User reserves equipment on a schedule. We track equipment status. We've got maintenance here. You know, we could easily create a flow modeled after our checkout flow to send this to maintenance, add some notes for it, or I could even just quickly, like, add some maintenance records for this the same way I do my transactions. Right?\u003C/p>\u003Cp>And I could even potentially just add those maintenance notes to this specific transaction. So as far as the equipment booking manager, in 1 hour or less, it's a win. It's good. Is it Checkroom? No, it's not.\u003C/p>\u003Cp>So if you need a more robust tool, make sure you check this one out. What was it again? Check Room? Looks like a pretty cool tool. Give it a free trial.\u003C/p>\u003Cp>I I like building inside Directus because, hey, bookings is is just one part of it. Right? You've got your other workflow. If you're managing your equipment, you're going to have to, plan where you're using that equipment at and things like that. So, inside Directus, in just a couple of tables, in less than an hour you can easily build an internal app to solve some of your problems.\u003C/p>\u003Cp>That's it. That's it for this episode of 100 Apps, 100 Hours. Thanks for joining me. Hope to see you on the next one.\u003C/p>","Hi. Welcome back to another episode of 100 apps, 100 hours, where we build some of your favorite apps or publicly fail trying. Today we've got a really neat use case that we're gonna try to build, an equipment booking manager. This sounds really fancy. I've seen this topic come up a few times within our direct us community, and I I thought, hey, let's tackle it today. It's perfect. So in all of the research I did, which is basically about 5 minutes before I pressed record on this, I found a comparable solution. It's called Checkroom, c h e q, Room. Looks like a pretty neat solution, but basically it's an internal app that allows you to manage all of your equipment. So equipment, in this case, it looks like they're targeting videographers, photography, you know, maybe studios, anybody who's got expensive camera equipment or video cameras, etcetera. But basically, you know, you can check these items in and out, reserve them for a time slot, manages all your inventory, what's the the status of all those, gives you a nice friendly dashboard. So that's what we're gonna be building today. If this is your first time watching 100 Apps 100 Hours, there are 2 rules. Number 1, we have 60 minutes to plan and build, so we're on a bit of a time crunch. There's no more, no less. And rule number 2 is, kind of, the anti rule. Use whatever we have at our disposal. So with that let's build our equipment booking manager. I'm gonna start the clock here and away we go. So I'm inside, Figma here or FigJam. This is how I like to plan before I build, just kind of quickly whiteboard things. So let's go in and just flesh out the the actual functionality that we're looking for out of this specific app. Right? We wanna track inventory of all of our different items, of all equipment and items. This could be, like, mobile phones or Android devices if we're doing web development for testing, camera equipment, printers, what whatever. We wanna be able to reserve equipment equipment on a schedule. We want to be able to track, details about the equipment. Let's say user can reserve the equipment on a schedule. We're gonna be able to track details of track equipment status. I mean, that's that's pretty much the the bare bones for this. You know, when are these things scheduled? Is it available for checkout? You know, maybe we wanna account for things like maintenance. Put, like, a big question mark there. Yeah. Okay. That that seems pretty good. Let's let's dive in and actually start planning what our data model may look like for something like this. So I'm just going to drag a nice square over here. Let's change this to red to kind of match. Alright. So thinking about this and and just talking it through out loud, obviously we have our inventory. This could be called items, could be called equipment. Let's let's go with equipment. That that seems like a a good compromise there. Items has a, kind of, a specific meaning inside Directus, which is just the actual individual pieces of content or data. So let's go with equipment, and then we're going to have, users. Alright. So that's gonna come actually from a Directus underscore user collection that Directus is gonna give us out of the box, which again is really nice. We got that authentication tied to that as well. And then we're gonna have, what, bookings for each of the this piece of equipment. Right? So we have a relationship here between equipment and bookings. The user creates a booking for a specific piece of equipment. You know, we have a check-in, check out type of functionality. It's great. Yeah. And this could be, you know, one of the other things that I saw here on the check room website, where was this? Inventory. One inventory to rule them all. You could potentially bundle these things up. Capture the flag. So, you know, maybe some type of checklist. Right? Let's go through and and just start fleshing this out. Right? We're about 4 minutes in. We need to actually start building something. So I'm gonna pull up my instance of Directus, so this side by side. We'll zoom in just a little bit. And this is just a blank direct assistance that I've spun up here locally, if I can remember the actual password. I think it's real secure. It's probably just password. Great. So we've got a totally blank instance of Directus. Let's start building something. Let's start with equipment. Right. We're going to just call this table equipment. Usually I've got a plural at the end of a lot of my tables, but equipments doesn't make a ton of sense to me. So, what are we going to have on this? We're going to have a status, like the default status field here. These just make it easy to add some of the most common functionality you'll have. It uses, like, a draft, published, archived. So I'm just gonna leave that blank for now. The sort field, do we really need a a sort for our equipment? Probably not. It might be good to track when it was created, so when it was added to the database, who it was created by, and the last time that information was updated. So we'll just roll with that. And let's give this, a name, right? This is the name of this piece of equipment, it's just gonna be a string. Really simple. No need to get fancy with it. Let's require a name for it. And next, let's probably add a a serial number. We need a unique identifier for this. You know, maybe we want, we end up printing these on labels or QR codes. Serial number would be a good way to track that. And, you know, we can choose to require that as a value, you know, maybe we don't necessarily need to, but we'll add that. What else do we wanna add here? We want to add a description. So maybe that's gonna be a wysiwyg editor. You know, this could be a HTML description, so we'll go with that. The type is gonna be text. I could customize my toolbar if I want to limit the options that we've got, but we'll just roll with the the default selections for now. We're probably going to have, what, some images of this or at least one image. You know, maybe we've got a gallery, but let's just call this the image. That's gonna be a relational field, Just a mini to 1 to our directus files collection. Make sure we can add an image for it. What else am I forgetting? We've got a status field. So, let's use a drop down for this. We'll call it status. The type, we're just gonna store that as a string. And for our choices, right, when we think through the choices, the equipment is either currently available, it is unavailable, you know, maybe it could be like, hey, it's getting serviced or it's in maintenance. I'm not sure if that's worthwhile to track or not. Alright. So let's just add a new one. We're gonna call it available. For the value, we can use lower case. Let's give a, maybe, a check mark. Hey. This is available. We'll make it green. Then we'll have unavailable. I'm sure there's probably a better naming convention here, but this is what we'll roll with. Maybe like a x. Okay. This is unavailable. And, yeah, maybe we do it like a status for maintenance just to differentiate. Maintenance. Wrench, maybe. Let's look for a wrench repair. There we go. We got a toolbox. That'll work. Okay. And I think yellow or orange is a good color for that. Great. So we've got available, meaning it's currently in inventory. Unavailable means it's out, Not in inventory. And then we have maintenance. Don't need to allow any other values. Looks great. Trying to think of what else we actually need for this data model. You know, we may have, like, specific details that we wanted to track, those are escaping me at this particular moment. So we've got equipment. Let's go in and add a few pieces of equipment. You know, we'll just start out and say, this is available. We have a, just looking at my desk here, I have an iPad Pro that is BG iPad Pro. Just create a serial number. The most amazing iPad you've ever seen. And we'll just go to Google, and it's still an image. Right? IPad Pro. Perfect. Where are you? Apple's gonna sue me, I'm sure. Please don't. Alright. Swipe an image here. That's great. We've got an iPad Pro. I may want to adjust my view just a little bit. I can't I don't see those nice images. I could go in and add my thumbnail for this. Looks great. Cool. We can make this a larger view. We can make it comfortable, and I can see a little more of that. Or I could even go straight to cards view here and get, just kind of like a gallery of where we're at. So the subtitle here could be the status, it's available. Great. We could add one more piece of equipment. You know, maybe I've got a, show my age here. The Sony a 64100. Sony a 64100. We could call this 1. Great. So we'll find a image of the Sony. I think it's the a 64100. Yep. That's the one. Copy image address. We'll just pull that in. Great. And, yeah, maybe I've got something like 2 of these cameras. How can I, like, achieve that really quickly? How can I duplicate these inside Directus? Well, what I could do is just go ahead and change the name a little bit, change the serial number, and I could go into the 3 dots up here at the top and click save as copy. So now I've got 2 of those specific cameras, maybe one's available, one's not, one's currently checked out, the other isn't. Cool. Alright. So we've got some equipment. What is the next thing that we're going to build? Let's take a look at bookings. Right? So how is this going to work? We're going to maintain a schedule of bookings. We're probably going to use our calendar view inside Directus for this, and then we'll be able to check-in and out this equipment, or actually reserve this equipment. The check-in, check out part may be, something else a little different. So let's just call this table bookings, makes sense. For the primary key we'll just use an ID. I'll use generated UUID for that. For the status, right, Let's leave status off here. We're just gonna check the box for all of the date created, date updated, all these system fields that auto populate when certain actions happen. And now we've got a booking, right, so when is the we we need the times for the booking. So let's do, like, a scheduled from. We use a time stamp format just so we get the time zone value. K. Let's make that high second half width. We'll do schedule 2 for that as well. Schedule from, schedule 2. Do we have notes? You know, I'm thinking, like, hey, we may have to have some type of notes. We'll use a text area for that. I don't need these to be formatted in a WYSIWYG or markdown. You know, could be potentially nice if you need that level of functionality. Alright. So we've got a booking, we've got schedule from, schedule to. We need, obviously, a piece of equipment tied to this booking. So now we're gonna start reaching for our relationships inside Directus, because each booking could only have a single piece of equipment, or could it? You know, we could potentially book a 3 or 4 pieces of equipment, but maybe those would be individual bookings as well. You know, it gets more complicated when you have a kit. Maybe we'll try to explore that. But for now, let's just keep it simple. We'll do a mini to 1. Or would it be, yeah, let's do mini to 1 for now. The related collection will be equipment. And we'll go into the relationship here. I can see what this setup looks like, but I can go in and also add that inverse relationship. So, I can add equipment to my bookings or add bookings inside my equipment. So I if I pull up a piece of equipment, I can see all the bookings right inside that item detail page that that that particular piece of equipment has had. Alright. Long winded explanation there, Brian. Alright. So we've got a schedule from, schedule 2, notes, equipment. Who is this assigned to, or who's pulling this equipment? I again, naming things is is probably the hardest challenge in development. Let's just call this assigned to or bookie. Call here bookie. Let's call it assign to. That's fine. So now I can't see my users collection here. Directus users is actually collection. So we're just gonna go in and expand that system drop down, pick Directus users. I could control the display template here, so what actually shows up. You know, maybe I want to show the image of the user, the first name, the last name. Great. And save it. Alright. So we've got the schedule from, schedule to. We probably need a status on this as well. Right? So who is this booking for? Alright. The status, when we think about that, again, let's use a drop down just because we get, like, a rich look at it, now that we have the ability to add icons and colors. But when I think about this, let's say, what's the initial state on a booking? Would be new or maybe unconfirmed is what I'm thinking here. So a new booking comes in, somebody has to verify that that booking is okay, or maybe we could do that automatically via flow. But we'll call it unconfirmed. And let's just see if we've got, like, a there we go. We've got a new flow. Fiber new. That's an interesting name for that particular icon. What else do we have? Scheduled. Scheduled. K. Time. See what we got. Okay. Let's just do, like, blue. That's kind of like a neutral color as far as what's going on. You know, maybe in progress is the next one. We'll just create some text and value for that. Settings. Maybe, like, a gear, progress bar, meter, bar. Yeah. There we go. That'll work. Alright. We'll mark that as I guess, as green in the state of a booking. Maybe it's red. In progress. Maybe completed is the state. Alright. So this is a completed booking. Check mark. I think that's a that'll be a nice icon for this. Alright. Completed booking. We'll go to check mark. We'll make that green. Great. Okay. So now we've got our status field filled out. We'll hit save, and now we can start looking at at how this is gonna come together. So we've got equipment, we've got our bookings, you know, maybe we look at this on a calendar view. Our layout options here we're gonna adjust this view. Each one of these layouts inside Directus has, separate configuration options that are dependent on the specific view. So here maybe we display, let's see. We're gonna display the equipment name. That makes sense to me. And then let's also maybe display the user as well, so who this is assigned to. Assign to first name. Assign to so we'll reach into that related collection, and I can display those related values. I thought I had added that. Assign to last name. Oh, I'm just not seeing it there. First name, assign to first name. I added it, I just didn't see it. Last name. Okay. Alright. The start date field is gonna be scheduled from, scheduled to. 1st day of the week is Sunday, and then we're good. Right? So let's go in and create our first booking. We'll just do this manually. And I can actually do default values as well, one of the handy things about when when you're setting up your data model inside Directus. If I go into my status, I've got unconfirmed as a value. If I go to my schema, I can just set the default value to be unconfirmed, and now whenever I create a new booking, the default is unconfirmed. Alright. So we're going to pick a piece of equipment. Let's say we want this Sony a 64100. That's gonna be assigned to me. I wanna check this out, for 2 days at near the end of this month and using for a photo shoot. Great. So now I could see that booking on my calendar, and presumably as these bookings come in, you know, I could go in and and see this schedule. Right? This is really a bare bones functionality. You know, I I could give access to our team to this and set up some separate permissions, but now, I let's maybe add some automation to manage this booking process. Right? It seems kind of hey, the flow doesn't seem quite right to me. So we wouldn't have somebody go in and manually adjust the status for their booking. You know, maybe we can take advantage of Directus flows and and build something to to, account for this. Right? So we'll go inside flows, and let's just let's think about how to manage the bookings. Each one of these specific bookings, has some actions that you're taking against it. So to me, if you're used to building, like, inventory systems, you probably got some type of transaction against that inventory of, hey, this was checked in, this was checked out, it was reserved, or it the on hand quantity was reduced. So maybe we do something like that where we track the individual transactions against these bookings of when it was checked out, when it was checked in. Let's do that. So we'll do bookings underscore transactions, and I'll just use UUID again. Just kind of the standard for me. Maybe the created on do we really need, alright. We're not gonna update these transactions. So, yeah, if we're created on, I'm just gonna change this to timestamp because that is the timestamp this particular transaction occurred. We'll use created by just so we have the user created that particular transaction, so if that was me checking myself in or somebody else checking in that booking. Alright. So at that point we also are gonna have a relationship to our bookings. So let's go ahead and set that up. Here it's going to be a mini to one relationship from booking transactions to bookings, because, a a transaction only has one booking, but a booking could have many different transactions attached to it. So we'll call this the booking, and our related collection is gonna be bookings. And for the display template, let's say that is the equipment name, space, date, schedule from. And we do schedule to. And if I wanted to, I can even go in and add the the specific user. So we'll do first name, assign to last name. Perfect. Alright. Cool. So now we've got a booking, and I forgot to create the inverse relationship. There's when you're creating that mini to 1, there's a second tab for that, but no fear. We can just go into our bookings and and we can add that from here. So we'll do the one to many field. So this is the inverse relationship. We'll call this our transactions. And let's do booking transactions and the foreign key we'll already have, which is the booking. So this is the foreign key for bookings inside transactions. We'll just save that, maybe show a link to it and hit save. Alright. So now if we take a look at one of our bookings we can see this transaction table. Alright. The other thing that we're probably gonna need on our booking transactions, and honestly, like, this may be hidden behind the scenes, and I'm just gonna go in and add some icons to this before it drives me crazy. Recovering designer folks. Bear with me. Camera. There we go. Looks great. Alright. So on our booking transactions, and I may even just nest that, we're gonna need a action type. Alright. So it's a check-in, check out. You know, move to maintenance, maybe, as well. For this, let's try something like radio buttons. So this will be the action. The choices that we'll have will be check-in, the value here, check underscore in, Check out. Check out. Maintenance. Move to maintenance. Request maintenance. Let's just call it maintenance. I'm getting too fancy with it. Taking too much time when I'm actually maintenance. I'm not even sure if that's the correct spelling. That's what we're rolling with. Alright. So now, what have we done? We've got some equipment, we've got bookings we can use to manage that, and then we can attach transactions to those. Right? So as far as our underlying data model, this looks pretty cool. Let's work on our our flow. Right? How can we make this easier? So I can imagine, like, if I'm looking up bookings, you know, maybe I want to bookmark this. This is the calendar view. So we'll add a calendar icon for that as well. Alright. So when I save a bookmark, that gives me the calendar here. And then maybe I can go in and maybe we just want, like, a list of all the bookings as well. This could be a new potential bookmark. Let's go in. We don't wanna update that original bookmark. I go back to bookings. We'll just create a bookmark for this. We'll call this list. This is a list of bookings. Great. So one of the other nice things about creating these bookmarks inside Directus is if you have certain filters applied or, you know, even a search query, you could save that search query and create these really nice slices of your data. But as far as my list view, you know, maybe I want to clean this up a little bit. My assign to field looks kind of boring, my equipment is not showing. Let's just make this look a little nicer. So I'm going to go back inside my data model and where I have equipment I'm gonna look at my interface and display. So here we have our interface, which is gonna be how it looks inside the detail view. The display is gonna be how it presents on the layout. So I could go in and maybe we want to show the, image, And I can expand that a little bit just to get a a thumbnail. And we'll show the name of this, and we'll do the same here. One of the little tricks I can use here is just drop this down, copy raw value, we can also paste it here and get access to the same thing. Great. As far as the assign to, maybe we want to do the same thing. Copy for the display. Because this is a user, I could also just show the direct us user. And maybe we wanna show the user in a circle instead of a square. Totally up to you. So there we go. This looks a little better. We'll probably have the schedule 2 on there as well. And maybe I wanted to get a little fancy with some conditional formatting for the status. Again, pretty easy to do. We'll just go to status, we'll do display, a couple of different ways I could display this. Let's just go with a label, and I'm just going to copy the raw value from these choices. I'm gonna paste it here and then, you know, I could adjust the actual colors, like background and foreground if I need to. But let's just see how this presents. Yeah. That looks that looks definitely a lot better, and nice to have like an icon as well. Alright. So now we've cleaned this up a bit. Let's build a flow to check-in and out. So what we'll do, we'll go into our flows. Directus Flows is a great workflow automation tool. So I can set up, flows to run on any number of things. This one we're going to use the manual trigger for, which is one that I use often. I really like this. So let's call this the check-in. And do we have, like, an in there's an in out. Maybe that's, like, a in out burger, maybe? I don't know. Let's, let's do, like, an arrow forward for check-in. That looks good. And then when we do a checkout flow we can move backwards. So I move on to the trigger setup. Again, you've got lots of options here. You've got event hook, you could, so basically when an event happens inside Directus we're going to start this specific flow. I could trigger on webhook requests from other third party services, a schedule, or in this case we're going to run this manually. Alright. So this is going to be on our bookings collection. Right? The location is going to be where this displays. So if I want to show it on the collection, like the view, the layout page, I can do collection page only or I can do item page only to show it with in a specific booking. Or if I do both, I can, get get the option to pick from the collection page or I could do it from the detail page as well. So great. We'll go in and let's just require confirmation on this specific booking and, you know, let's add some inputs for this. Let's add some notes. Any notes about this booking? So I'm trying to think of what this would be. You know, maybe, hey, they showed up late or, you know, something like that. But we're already gonna be picking a booking so we know who this is. Let's give this a shot. And the first time I'm, the first thing I do whenever I'm building flows is basically start with my trigger, and then I'm going to go in and actually trigger my flow. So over here on the right, I can see my flows, I can select this, right? If I unselect, I won't be able to check this in. But we'll just say, hey, I'm going to check this in and here's some notes about this. Great. Alright. So now if I go back into my Flows, we'll see a couple things. Right? We've got our logs that we're gonna take a look at. We've got our payload. And if I look at the body, we've got the notes, we've got our collection, and we've got our keys. So this is the actual ID of the booking that we selected. Great. So when I think through this flow, right, what are we going to do? There's a a couple things we're probably going to want to achieve with this flow. If we go back into our data model just thinking out loud here. Alright. We're going to this is getting in the way. Let's just do text. We're going to want to create a booking transaction. We're probably going to update the status, update the status of the booking, and then we're going to update the status of the underlying equipment. Right? So that's gonna move from available to unavailable. Alright. We can achieve all of those inside of Flow. Let's get to it. Alright. So we are going to create our first operation. Let's call this, create booking, create transaction. Okay. We're gonna create data. So we're going to create an item in the database that's going to be our booking transactions. We can leave the permission set to from trigger because we're going to run this flow inside Directus. If this was triggered on a webhook event or something like that, you would probably want to use full access because you're not going to provide that accountability, that direct us user to actually run this flow. But we'll go into our payload and listening through this, we've got a booking. So let's just throw that out there. And then that is going to be coming from our trigger. So whenever I want to access data from that trigger event, I have to use this special syntax and just add a dollar sign in front of it. That is the only time I need to use that to access data from this flow. So only in the trigger. And let me just take a look. It could be handy if you take and copy this, into Versus Code or your editor just to make it easier to remember what it was as you're building out these flows. So we've got the trigger dot body dot keys. This is an array. We're gonna have to pick off the first value of that array. Alright. So let's run this again. Create transaction, and we'll do booking transactions, and paste this in. Trigger dot body dot keys, and we're gonna grab the first item from that array. And again, we're using the mustache syntax here. We can close this, and we're also going to have an action. Alright. So if you remember, that action is going to be check-in. And is that all we need? I think that's all we need. Great. Alright. So the next thing that we're gonna do, we want to update the status of that booking. So this is a check-in. We are going to update the status of that booking to what? So if we look at our booking, we've got, we're gonna change that to in progress. Right? Great. So we'll do update our booking, and that's gonna be update data. So we'll just call that update booking. That's gonna be our bookings collection. Again, we'll use the trigger permissions. Okay. So 2 ways I could go about grabbing the ID of the item that we're gonna update. I could use the IDs field to specify the specific IDs, or I could run a query for this. I'm going to lean this way just because we already know what that value looks like. So it's going to be dollar sign trigger dot body dot keys, first item of that array, and I'm going to press enter to make sure we store that. I can also go in and edit the raw value here just to see what that looks like. So again, if I'm using this mustache syntax, it's going to dynamically populate that data for me. We do not want to omit events for this. We don't want to trigger any type of, like, infinite loop using that, so always be careful when you're using that. And for our payload here, we're just going to update the status of this booking. The status is going to be in underscore progress. And I think I think that's all we're really looking for here, right. Now, we need to update the underlying piece of equipment as well. And if you take a look at our log, though, right, we are not getting that information here. We're only getting the key for the booking. So in that case, before we can update the actual piece of equipment this is attached to, we probably need to get the booking so we can find that, equipment that's attached to it. So let's just create a new operation. We're going to call it find booking. We're going to use the read data operation. We're going to find bookings collection. And then, again, we're just gonna do the same one, trigger.body.keys, save that. And for our query here, I could pass any of the the standard query parameters that are available. If you're not familiar with those, check out our docs. But what I'm gonna do, I'm gonna pass a fields query, or fields property, and this is gonna be an array. I'm gonna get all the root level fields, that's what this asterisk will do, And then I'm also going to get all of the equipment fields as well. So I am drilling through my relationships here. One of my favorite features of Directus is the ability to get and expand those relationships in a single API call. So assuming that goes well, then we are going to update that piece of equipment. Right. Before we do that, let's just stop and go test this out. Alright. So we are gonna go here. I'm gonna hit check-in. Here's some notes, and, you know, I could also save these on that transaction. We'll maybe do that in a moment. But we can see here the change. We've got a status of in progress. We've got a new transaction here. We can see the time stamp that was created. It was a check-in action. Let's go into that booking transactions. We'll add that notes field as well, just so we can store those notes. Great. And let's run into our flows and just see the log. Right? What did we get for the booking? Are we getting the data that we want? We can see the equipment dot ID. That's what we wanna update, and we're gonna change that status to available or unavailable because it it's unavailable. Alright. So this last link in the chain here will go in and update equipment. And You can see Directus will automatically generate the key for me. If you want to change that, feel free to. Not a big deal. But these keys are how you access the data in subsequent operations. Right. So as you can see in just a moment, we're going to go into choose our equipment collection. We'll do from trigger. But as far as the ID's that we're going to update, we're going to use, find booking_orfindbooking.equipment.id. So that is how we're gonna access this. And for our payload, we're just gonna change the status. Unavailable. Okay. Great. Cool. Alright. So I'm just gonna go in and manually change this. This is, let's call it a scheduled booking, for I'm gonna undo these transactions, unhide those just because I want to delete this one. Alright. So we're starting fresh. Everything's available. We've got a booking for our specific camera. I'm gonna go in, select this. I can also go into the item detail page here and do the same thing. And, oh, also forgot to update our notes. Right? Let's go ahead and do that as well. The perks of building on the fly. How are we looking time wise? Yeah, good. Pretty pretty solid time wise. Alright. So for our transactions here, we're gonna pass the notes, and that will be trigger, dollar sign trigger, dot body dot notes. Okay. Just some standard JSON format there. Great. Alright. Moment of truth. Let's see if this is actually gonna work. We'll go in, run check-in, our check-in flow. Bryant was late showing up to pick up this camera. Great. We'll hit run flow. Now we can see this is an in progress booking. If we look at our equipment, it shows as unavailable. You know, maybe we could potentially, show that a little nicer, maybe with a badge or something. And what else did we update? Right? We've got a transaction, we're storing our note. If we go into that detailed booking, we could see the transaction here. Maybe we want to clean this up a little bit for our users because we don't want them to see that UUID. Alright, so again, optimizing for our users. Let's go in and we'll go to the transactions. What do we want to do? We want to do show the timestamp, what the action was, and maybe if there's any notes. Oops. Let's do notes. See that? Cool. Okay. So now I can see this, 1 minute ago it was a check-in event, Bryant was late showing up to pick up this camera. And I can even go a step further with this, or I should be able to, in the action here for my display, I could show a formatted value of this. So we can show a border, and if this is a check-in, you know, maybe we wanna show, what did we use for this? Maybe, yeah, green will be fine. We could show a checkbox. Or, actually, I think we were using, like, a arrow. Right? Arrow. Check-in. And check out of it. So if the value equals that, we can actually, I'm getting these wrong, aren't I? We'd probably be checking out the equipment, not checking in. So maybe I need to go in and fix that as well. Arrow. Left. Right. There we go. Check-in. Oh, let's use left for that one. Great. Okay. Alright. Cool. So now we've got our actions. If I take a look at our transactions, we could see we got a badge for that, and I would probably actually flop these. Right? That would be this would be like a, yeah, checkout and not check-in. So we can change this. Right? This is a checkout event. And is there anything else we need to? I don't think we need to change any of these other ones. Alright. Cool. So let me go in and just fix this again. Alright. I'll delete this record so we don't have any more booking transactions there. Reset this guy to scheduled, and let's try it again. Check out. Late again. Run one flow on the selected item in progress. Now we got our checkout. Great. So I could do the same operation just in reverse for, like, a a check-in. So when we are checking this equipment back in, pretty easy to do. What other functionality do we need out of this? You know, is there an opportunity to take this one step further? We still got, about 15 minutes here. So what if we were to send some notifications on check-in or, like, if we got a new booking, we want to send some type of alert or or notification to someone. So maybe we reach into our flows for that. We'll go in and, you know, we could do a maybe like a a check-in flow as well. Right? So we do all this over again. We are going to do a check-in flow. Isn't that exciting? Let's do a notification. Right? New booking notification. Alright. So whenever a new booking occurs, we want to trigger a notification. That is going to be using our event hook, and you get 2 types of those event hooks inside Directus, whether that's a filter, which is a blocking. So this is great if I want to run some type of logic and then potentially either adjust the payload of the item that we're saving or, you know, updating, deleting, etcetera, or, you know, if I want to actually cancel that event pending some logic. In this case, we're going to do action non blocking. We don't want to block anything from occurring. Just after a new booking occurs, we are going to run a notification to a user. Right? So our scope here is items dot create. That's gonna be on a new booking And this also could be we could condition this down if we wanted to, where we only trigger on unconfirmed bookings. Unconfirmed. Alright. So the condition gives us, if, then, or if, else logic. So I can separate my flow into 2 separate paths, right. If the condition passes, we go this way and we run some other operations. If it fails, we do something else. So here's the syntax for this. We're just gonna use trigger. I'm breaking my own rules here. Let's just save this as is, and let's actually go create a new booking. And let's do it for the iPad Pro. We'll do the rest of this week. That's gonna be assigned to me. And here's some notes. Alright. So we created a new booking. I'm gonna get, a a payload here that I can take a look at, right. So the the payload is a little different than what we get from when we manually trigger these. But we can see we've got the equipment, we've got schedule from, assigned to. Great. So only on unconfirmed, what are we going to trigger this on? Basically, the unconfirmed. I really don't even think we need a condition here because the default value is unconfirmed. So, yeah, we could we could potentially do something like this. Like status is I think it's not equal to unconfirmed. Let's see if that runs. And then we're going to notify a certain member of the team. Right? Let's send a notify Matt. I'm going to pick on Matt on my team. We're going to send Matt a notification. And to do that, we have to have a UUID for that user. And I don't actually have a user in this account for Matt. Maybe we'll just use myself. Or we'll just create 1. Matt Minor. Great. Alright. We'll pick up the UUID from that specific user. And, you know, part of this flow could be like picking that up dynamically, depending on how you decide the logic of who to notify here. But I could just hard code that value in, hit save or hit enter to record that, and then we're going to add a notification. Right. New booking. Please review this booking and confirm. And as far as the collection here, the item, we are going to do what? Let's look at our log here. We got the payload. We're gonna do the key. So that's the created booking. And I can link that notification to that specific item. So it looks something like this, where we have trigger dot key. And that should do the trick. Alright. One of the other things that we may want to do, let's just check time really quick, we've got about 10 minutes. One of the other things that we may want to do is lock down some of these fields when you're actually editing this data. Right? When I am looking at a booking, I may not want anybody to be able to update the status without going through a specific, like, check-in or check out flow that handles all the logic that we want. Or likewise, you know, I I don't want any of my non admin users to be able to change the assignment for this. So there's 2 ways I could do this. If I go through the actual data model, you know, if if the booking status is controlled almost exclusively through Flows, I could go in and I could disable editing for this value. That'll be across the board, basically. Inside the UI nobody can update this value. So this is what that looks like. Now if I go into status, it just shows me the read only field, and if I wanted to change the status I would have one of my flows or update this via the API. Right. Here's my notes. I run this flow. My status is changed to in progress, and, potentially, I've got some type of, operation here to move this, like my check-in process. Right. But the other ways I could do this are via the Directus access control. So I could go in and create a, you know, let's call it a team member role. They will have app access, they won't have admin access, so they can't adjust the flows, they can't adjust our data model, but we do want them to be able to do certain things, like, view all of our bookings. Right. So I can give all access to start with, maybe we start whittling down some of these permissions. There's no need to share it. I don't want them to be able to delete bookings, delete equipment. I do want them to be able to update these fields. It could potentially mess with our flows as well, but let's address it. Right. So we're going to use the custom permissions for our bookings, the field permissions oh, I've gotta go in and do no access. Alright. So here's what we could do. We want to allow them to update only certain fields. Right. Maybe they can change the equipment in case we need to swap that. They can't change the status. They can change they can't change the assign to. They can't adjust the transactions, but they can see them. Sounds great. I can even configure field validation rules, or presets. Like, hey, here's the the default values for those. The only other thing that I'm concerned with here is because we're locking down that status, inside our flow for checkout we're actually using the permissions from the trigger here. So we'll just do full access, which basically gives unlimited access to, update these things. Great. Cool. And then, you know, if we go back into access control or team member, maybe we don't want them to update the equipment status either. Alright. So we'll give access field permissions. These are the fields that this role can update. They can update the name, serial number, description, image. I can't update the bookings. Status, no. Okay. Obviously they can update these. Cool. Alright. So we'll save this And now let's open up an incognito window. But first I need to give mister Matt Minor some permissions, or just a login. Matt@example.com. Give it a password. Great. Open up this incognito window. Go local. And we'll do matt@example.com. Password. Password. Did I did I get the password wrong? Did I even give them a role? So a couple things. I need to give Matt that team member role so he can have app access. And, you know, maybe I typed the password wrong. Password. Let's just copy that and make sure. Hit save. Okay. There we go. Alright. So now this is what Matt would see when he's logged in. You know, immediately you'd see he doesn't have access to the admin interface. But if he goes within the bookings, he can't update the status, he can't change the assignments, he can't do any of this stuff. But what he can do is run this flow. Here's some notes. This is not gonna really change any of this. Let's just zoom back out. Too zoomed in. Alright. So for the iPad Pro, I'm just gonna change this did I lock that down? I totally locked that down, didn't I? Let's go here. We'll change status. Go to the disable editing value. I'm just going to remove that just to demonstrate this capability. Alright. So now we go to iPad Pro. You can see over here I'm logged in as admin on the right hand side over here. I can change this status to be whatever I want. Hey, this is scheduled. I've got full permissions. Matt, when he's logged in, he can't edit any of the specific values. Right? But what he can do, he can still run these flows. And here's my notes about this booking. Right. We run the flow. It still updates all of our data, but in a restricted manner. You know, it only runs the logic that we want to run, so we can force Matt down a particular path that we want him to go. Alright. So as far as equipment manager, I'm calling this a win. We have got our inventory. Let's look at our list. Right? Track inventory of all of our equipment. User reserves equipment on a schedule. We track equipment status. We've got maintenance here. You know, we could easily create a flow modeled after our checkout flow to send this to maintenance, add some notes for it, or I could even just quickly, like, add some maintenance records for this the same way I do my transactions. Right? And I could even potentially just add those maintenance notes to this specific transaction. So as far as the equipment booking manager, in 1 hour or less, it's a win. It's good. Is it Checkroom? No, it's not. So if you need a more robust tool, make sure you check this one out. What was it again? Check Room? Looks like a pretty cool tool. Give it a free trial. I I like building inside Directus because, hey, bookings is is just one part of it. Right? You've got your other workflow. If you're managing your equipment, you're going to have to, plan where you're using that equipment at and things like that. So, inside Directus, in just a couple of tables, in less than an hour you can easily build an internal app to solve some of your problems. That's it. That's it for this episode of 100 Apps, 100 Hours. Thanks for joining me. Hope to see you on the next one.","published",[135],{"people_id":136},{"id":137,"first_name":138,"last_name":139,"avatar":140,"bio":141,"links":142},"791e1503-1d88-463d-9347-0b9192933576","Bryant","Gillespie","9013afc8-e8d7-4182-9b18-44db08117bb9","Developer Advocate at Directus",[143,146],{"url":144,"service":145},"https://directus.io/team/bryant-gillespie","website",{"service":147,"url":148},"github","https://github.com/bryantgillespie",[],{"id":151,"number":128,"year":152,"episodes":153,"show":163},"14fda5f2-95de-4dbe-a4e2-3fd956c21c19","2024",[154,122,155,156,157,158,159,160,161,162],"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","b9f1d4cf-f53c-49db-9e87-adf7e3b9ff99","aad8d674-2b58-4604-8e43-b98f7c6e05cb","6bff0c09-ad87-4d5c-b227-89b8c3c02220","6fb9aa9a-2b59-44b6-b78f-d1831fa657c6","620cf225-a23a-415a-ad95-9ba8e2dec984","b8b36125-7a4a-40e4-85f6-f4fe9138085e","385bdd7d-038d-4f9c-8037-357e5272420a","383c24d5-b6b5-4d66-aba6-6997af5f77b4",{"title":164,"tile":165},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"title":167,"meta_description":168},"Build an Equipment Booking Manager","Bryant tackles building an equipment booking manager. Watch as he designs and implement a system to manage videography and photography gear, including features for checking items in and out, reserving equipment, and maintaining a comprehensive inventory dashboard.",{"id":170,"slug":171,"season":172,"vimeo_id":173,"description":174,"tile":175,"length":176,"resources":8,"people":8,"episode_number":177,"published":178,"title":179,"video_transcript_html":180,"video_transcript_text":181,"content":8,"seo":182,"status":133,"episode_people":183,"recommendations":185},"ec88bef1-fffd-43eb-9d93-3123dc381b97","ai-letters-to-santa","d6b229fe-38fc-495b-ba0c-c574ebfea38f","1059428648","Bryant builds a holiday-themed app that generates personalized letters from \"Open Source Santa\" based on GitHub profiles. Watch as he creates a system that analyzes developers' repositories, determines whether they're on the open source naughty or nice list, and generates snarky, sarcastic letters from Santa — complete with festive styling and holiday cheer.","6209314e-e6ee-4a2d-9e97-11eedd08595a",59,1,"2025-03-10","Mission: AI Letters to Santa","\u003Cp>Speaker 0: Alright. Alright. Alright. We are back with the Christmas edition of 100 apps, one hundred hours. Today, we are going to be building AI letters from Santa.\u003C/p>\u003Cp>I've got my lumberjack style on today. My wife called this lumberjack Jesus earlier, but I digress. We're back for more. The rules of 100 apps, one hundred hours. If this is your first show, we have sixty minutes to plan and build an application, a website, a portal, whatever.\u003C/p>\u003Cp>Whatever we're building, sixty minutes, no more, no less. And rule number two, the anti rule, use whatever you have at your disposal. And since this is an AI Christmas special, I'm gonna pull out all the stops. So let's dive right in. We're gonna hit the clock here.\u003C/p>\u003Cp>Fire it up. Sixty minutes on the timer. Go. Alright. So AI letters from Santa.\u003C/p>\u003Cp>What do we actually want out of this? So I have to admit, I cheated a little bit because I thought about this with my team, and I knew we wanted to do this. I've seen things in the past where you write a letter to Santa, you get something back in the mail, etcetera, etcetera. With AI, we could take this up a notch. So combining two ideas.\u003C/p>\u003Cp>A while back, I saw a GitHub roast page where you enter in your GitHub profile and it, basically will scrape that and give you a roast of how well you're actually not doing in GitHub. So we're gonna combine that with a letter from Santa. And basically, what we wanna do is, enter a GitHub profile. We're gonna scrape that profile. We're going to send that to AI.\u003C/p>\u003Cp>So we want LLM analysis of the profile. I'm not sure what we're gonna call that. And then I'm going to bucket people on the open source naughty or nice list. So score naughty or nice list. And then we're gonna generate a letter from open source Santa.\u003C/p>\u003Cp>Generate a letter from open source Santa to that GitHub profile to that profile. Alright. So as far as that functionality, this looks pretty good. Right? What are the tools that we're gonna use of the trade today?\u003C/p>\u003Cp>I've got a Directus Docker container up and running locally. Directus is obviously the back end we're using to store all of these things. And if everything works as intended p m p m g. I guess, sometimes things don't work as you intend. I've got a Nuxt application that we are going to try and use here.\u003C/p>\u003Cp>I'm not sure what's going on, but let's hop into the Directus instance. So I'm just gonna pull up Chrome. We'll log in to 8055, and I should be able to pull up my back end. So great. Got Directus running.\u003C/p>\u003Cp>You could see this is a pretty blank instance of Directus. This is just the boilerplate I use now. There are a couple extensions installed that I was testing, just messing around with. But, let's make sure. What are we doing here?\u003C/p>\u003Cp>For MPMI. Sometimes these things never go as planned. Okay. So maybe now we can get this Nuxt application up and running that will be served at local host 3,000, and we'll just basically use it to scaffold out our communications. As far as what I'm using, I've upgraded this boilerplate that I've used for 100 apps to, the Nuxt UI v three alpha, just to play around with Tailwind four and, you know, some of these nice new components that are coming from, like, Radix view.\u003C/p>\u003Cp>So alright. Let's actually model this thing out. Right? What do we need as far as our data models? I think we just need, like, a maybe like a profiles.\u003C/p>\u003Cp>So under profiles, we would have, what, our username, letter from Santa, letter from Santa, list, you know, are you naughty or nice? Great. And what else do we need? Let's let's jam on that. So we'll, just set up the back end for this.\u003C/p>\u003Cp>I'm just gonna create a new collection. We're going to call this profiles is the name of it. And why can't I zoom way in? There we go. That's maybe too far, but all good.\u003C/p>\u003Cp>Let's do created at, updated at. K. Status sort not needed. Who this was created by, I'm not super concerned with. So now we have a profile.\u003C/p>\u003Cp>We're gonna do the username. Great. That's where we'll store the GitHub profile. What else do we need? What else did we have here?\u003C/p>\u003Cp>We've got the letter from Santa. What is that gonna be in letter from let's just call it letter. Great. We'll use the WYSIWYG editor inside Directus so we could just store, I'm assuming, HTML content for that. And then we've got the list.\u003C/p>\u003Cp>So that's basically gonna be a string. We can, you know, make this look nice inside the directus admin. We'll just give it a naughty. Feel naughty just typing that out, and then we have the nice list. Great.\u003C/p>\u003Cp>There we go. And I'm just going to go on record that we're probably as soon as we start typing naughty into the AI stuff, we'll probably get some some things back. Like a I probably set off the content alarms or something like that. So there we go. We've got a username.\u003C/p>\u003Cp>We've got a letter. We've got a list. You know, I could potentially put that in here. What I'm gonna do now, I'm just gonna let's just we're just using Directus to store this at the moment. Right?\u003C/p>\u003Cp>There's a lot of different ways I could go with how to actually generate the application here. But Directus allows me to create custom extensions. What I'm gonna do here is just start, working on this from the Nuxt side of it. So we're gonna input the, actual form here. Let's add a profile.\u003C/p>\u003Cp>What are we gonna call this? Let's just call this letters dot view. We'll get a view component set up. Lang equals TS. Great.\u003C/p>\u003Cp>I need to work on my little macros here. Okay. Alright. The other thing that you'll notice here is that I am using cursor. So cursor I recently started testing this thing out.\u003C/p>\u003Cp>Really enjoying the actual auto completions for this thing. So, I don't have it usually generate like a a giant list of code, but the automations, or the auto completions are are pretty nice for this thing. So let's start with, what, step one. It'd be enter GitHub profile info. GitHub username.\u003C/p>\u003Cp>Alright. So the only thing here, sometimes it gets a little wonky with the okay. So we use the you form from Nuxt UI. Good question, Brian. The new one, the alpha, they changed some of the conventions.\u003C/p>\u003Cp>So I've got a form with a schema. I've got a form field instead of a form group, and then I've got an input. Okay. So we've got the form, new form field, and input GitHub username. Let's just see what that gets us on the front end.\u003C/p>\u003Cp>We're gonna go to this page, which is letters. Okay. Alright. So let's go ahead and just center this up. I think there's actually a container component we can use.\u003C/p>\u003Cp>Great. Cool. Okay. So now we have a GitHub username, and let's add a submit button. New button, click handle submit, and boom.\u003C/p>\u003Cp>We have a GitHub username, blah blah blah. Hit submit. Supposedly does something. What it's gonna do right now? Absolutely nothing.\u003C/p>\u003Cp>Alright. So the next thing that we wanna do, let's kick this thing off. We want to have the form state, we use reactive for that. Great. GitHub username.\u003C/p>\u003Cp>Okay. And then we're gonna write a function to handle submit. Thank you. Yeah. Great.\u003C/p>\u003Cp>We'll just, console log that. Right? Boom. There we go. We can see the GitHub username, yada yada yada.\u003C/p>\u003Cp>Alright. This is actually gonna be an async function. Great. Okay. So now what do we wanna do with this?\u003C/p>\u003Cp>Right? We have to think about our application structure. And what I'm gonna do here is just basically add a Nuxt server route. So if we break this down, in this server route, what we're gonna do, call the GitHub API, call GitHub API. We're gonna wanna grab a couple pieces of information like the user profile, or any other repos, and maybe, like, their their public read me.\u003C/p>\u003Cp>I guess we could loop through the actual repos and, you know, pick up more information there, but, let's see what we can get done with that piece. Alright. So let's just go here. We're gonna set up a new route. Let's call it, roast route.\u003C/p>\u003Cp>We'll do post, and just gonna copy the event handler here. K. So now whenever we hit this route with a post, it should return hello world. We can just check and see if that's actually gonna work. So, we will do the I'm trying to think if that's gonna return.\u003C/p>\u003Cp>Nope. So we got the response. We're gonna do await. We can use the regular fetch or the dollar sign fetch, which is the Nuxt specific version. And just test this out, see what we get back in the console.\u003C/p>\u003Cp>Where are you? Okay. Yeah. So we can see the request going out. We can see hello world coming back.\u003C/p>\u003Cp>Great. Cool. Alright. So now what we're gonna do, right, let's just scaffold this out. We are going to pick up the body.\u003C/p>\u003Cp>There's a wait read body. Great. So that is going to have the GitHub username in there. And, how did we spell that? Yep.\u003C/p>\u003Cp>Great. Alright. So we're gonna say GitHub username, and then we've got, like, this git roast function. I'm not really sure where some of these auto completions are coming from. But, what we're gonna do next, let's call yep.\u003C/p>\u003Cp>There we go. That's a good one. API users, GitHub username. Is that the correct one? Let's just test that.\u003C/p>\u003Cp>All the developers on my team are screaming and crying at the moment, watching all these AI auto completions. So that seems fair. Great. And let's actually use the Nuxt equivalent. Just this is using OFETCH, which does some automated data transformation and should automatically throw errors for you as well, which is nice.\u003C/p>\u003Cp>So this is gonna be the let's call this a profile. Alright. And then if we take a look at the profile, we probably wanna get the actual repos for that user as well. Alright. So we'll get the repos.\u003C/p>\u003Cp>Great. And let's just take a look at the data we're getting from the actual repos. Okay. So what do we actually concern ourself with here? Do we actually want all of this information?\u003C/p>\u003Cp>What do we actually care about from these? So stargazers, watchers counts, maybe those properties. You know what? Let's just jam it all in there and see what comes out of it. Right?\u003C/p>\u003Cp>And then let's get the profile readme. GitHub user content, GitHub username, repos dot name, Repos dot name. No. That's not gonna cut it. I think it's gonna be, what, GitHub username.\u003C/p>\u003Cp>GitHub username. Somebody who's already done this before. Give me the structure. And then main. Let's just see if we can find that.\u003C/p>\u003Cp>Read me will just populate my name. I don't even know if I have a actually have a read me. Yeah. There we go. Okay.\u003C/p>\u003Cp>So that is the structure. Great. That is what we needed to confirm. And now let's just actually return this and see what we'll get back. Alright.\u003C/p>\u003Cp>Brian Gillespie. Now I'm gonna fire this away. Roast. No. Nothing found.\u003C/p>\u003Cp>Well, that's a little concerning. GitHub username equals body. Read the body. We have fetched the user's GitHub username. Let's just console log the username.\u003C/p>\u003Cp>API dot GitHub users. I don't see the actual username coming back. GitHub. That's always fun. Alright.\u003C/p>\u003Cp>What did I do wrong? GitHub underscore username. Okay. Oh, duh. Are we actually passing that in the body of the form?\u003C/p>\u003Cp>Form. Console dot log response. Request payload. API slash roast dot post. What are we getting back here?\u003C/p>\u003Cp>GitHub username form dot GitHub username. Oh, that's right. We are missing a state variable here. So is it actually submitting the form? No?\u003C/p>\u003Cp>No. Okay, friends. What do we do from here? We have handle submit. We're going to use fetch await.\u003C/p>\u003Cp>Wait that fetch request. We should have already got this back. Of course. There it is. What a dumb dumb.\u003C/p>\u003Cp>Forgot to actually fix the v model there. So that's what that is. Sometimes, these are not great to do at the end of the day. But okay. Where we at as far as time?\u003C/p>\u003Cp>We've got forty two minutes remaining. I feel pretty confident on this one. Alright. Now with our roast, we can remove this. We should be able to get that information.\u003C/p>\u003Cp>Now let's make sure that we're getting what we want back from that API. Great. There's the profile. There's the repos, and there's the profile. Read me.\u003C/p>\u003Cp>Great. Alright. So what are we gonna do with these now? Right? The next step in this process would be to, pass the profile to LLM and ask it to summarize for us.\u003C/p>\u003Cp>What do we want this to return? It should return something that looks like this. We want a letter from Santa. Letter from Santa in HTML. And then we're gonna want the the list, naughty or nice.\u003C/p>\u003Cp>And that should be all we really need to return. Alright. LLM returns JSON. Cool. So what is the LLM we're gonna use?\u003C/p>\u003Cp>You know, typically, I use OpenAI for a lot of the stuff that I do here at Directus. I've been messing around a lot with, Claude locally. So we're just going to try this out. Santa letters. So we're gonna use anthropic.\u003C/p>\u003Cp>There's my API key. We're going to drop that in our ENV file, if I can actually get there. Jeez. There we go. I'm just gonna call it Claude ABI key.\u003C/p>\u003Cp>Okay. Great. And by the time you've watched this, hopefully, I've disabled that key. So, don't stop the video and try to figure that out. Alright.\u003C/p>\u003Cp>Let's pull up our docs for the API. We need to get the API reference. And let's define this prompt. Prompt. You are a letter writing AI.\u003C/p>\u003Cp>Alright. Analyze the following GitHub profile. You are the open source Santa Claus. You determine whose open source contributions are naughty or nice, analyze the following GitHub profile, Return a JSON object with the following fields, a letter from Santa and HTML. Set a really high bar for the nice list.\u003C/p>\u003Cp>What else do we need as far as a prompt? And, yeah, here is the data, profile, readme, json, stringify. Wonder why it's doing that. But okay. Nevertheless, there we go.\u003C/p>\u003Cp>Turn a JSON object instead of really write the letter in a snarky sarcastic tone. Cool. Alright. And now we're going to send that to Anthropic. Alright.\u003C/p>\u003Cp>So if we look at their oh, looks like we could just use their JavaScript SDK. That's great. Let's go ahead and open this up. We'll fire that up, install this thing. Import anthropic.\u003C/p>\u003Cp>Great. And then we're going to create that message. Alright. Constant AI response equals anthropic messages dot create Claude Sonnet. Okay.\u003C/p>\u003Cp>Messages user role, content prompt. Do we wanna set, like, max tokens? What is the what's the default for max tokens? Where do we actually pass this API key? Getting started authentication, x API key.\u003C/p>\u003Cp>API key equals process e n v. And, again, like, you could start to see why I really like using cursor because it has, like, this sixth sense for a lot of this stuff that I'm actually trying to do. Sometimes it gets that wrong, but a lot of times it gets it right. So alright, AI response messages. Do we wanna set a max tokens?\u003C/p>\u003Cp>Body messages, max tokens required. Let's give some more parameters. Write a short letter in a short in a snarky sarcastic tone. That is 500 words or less. And then for the tokens, if we look at Sonnet, we've got like a context window of like 200,000, so maybe a hundred thousand tokens.\u003C/p>\u003Cp>Oh, no. That's the output. Max output is eight nine one two. That's fine. Max tokens.\u003C/p>\u003Cp>Great. And let's return. Actually, what we're gonna do next is save that to the Directus database. Right? So we've got this collection for our profile.\u003C/p>\u003Cp>What I've also done, I've got a utility set up here. This is just using the Directus SDK. And one of the nice things about Nuxt, I say that a lot, is, the ability to it will auto import this for me. So I don't have to import it. I should just be able to call Directus server right here.\u003C/p>\u003Cp>So let's call it Directus response equals await directus server dot request create item. That's going to be in the profile. And we'll do the GitHub username. That's actually going to be username. Letter response, content dot text.\u003C/p>\u003Cp>I don't actually know what we're gonna get back directly. Return only a JSON object. And maybe we wanna add something like this for let's just do code. We'll set this up. And I'm just gonna add a field for, let's call it metadata or something where I'm just gonna store the entire response.\u003C/p>\u003Cp>And honestly, let's just do that to begin with. Metadata, AI response, content dot text. So if we take a look at the API reference, we go back to messages here. I'm kinda curious as to what we're gonna get back. The content text.\u003C/p>\u003Cp>Okay. Type text something. We'll get back something from the system. Let's just even do it this way. We'll say content direct us response, and then we're going to return direct us response.\u003C/p>\u003Cp>See what that gives us. Now let's go in. Where's our app? We'll switch back to Chrome. I do like Arc.\u003C/p>\u003Cp>I've found it to be lacking for development because it's just not super fast. Alright. So fingers crossed that this actually does what it should do. And, let's make this even nicer. And we'll add, like, a loading state, constant loading, ref equals false.\u003C/p>\u003Cp>We'll add loading dot value equals true. Loading dot value equals false. Great. And what else do we want to do? Is there a loading state on the actual form?\u003C/p>\u003Cp>Let's take a look. So Nuxt UI state, there is not a loading state on that. There should be on the button though. So just update that. Okay.\u003C/p>\u003Cp>And let's test this bad way out. Submit. Alright. We're waiting. We're waiting.\u003C/p>\u003Cp>We're waiting. We're waiting. We're waiting. This could take a minute. So, you know, we might even want to, like, potentially set up a oh, okay.\u003C/p>\u003Cp>So we're not getting anything back. We see a request error. So let's go into our roast, and we should probably do some error handling. Alright. Catch error, console error.\u003C/p>\u003Cp>Return, or we could just throw the error. What do we got here? Format. Alright. Let's refresh.\u003C/p>\u003Cp>I'll try this again and see what kind of error we're getting and why. Pending. Invalid user credentials for Directus. Okay. Great.\u003C/p>\u003Cp>So, just wasting tokens there, throwing them into the void. One of the things that you'll notice, I do have this direct as URL set up, but, my server token is probably a % not correct. So I'm gonna go in and create a token for this. We'll just create a new token. We'll call this the server token.\u003C/p>\u003Cp>And I wanna make sure in my utility that I have that set as server token, direct us URL. Okay. Let's try this thing again. PPM dev. I will restart the dev server, pull in that new ENV, though I think Nuxt may automatically update that for us.\u003C/p>\u003Cp>How we doing on time? We got twenty nine minutes left, so I'm feeling pretty confident that we can get something out of this. Let's go ahead and try it again. Bryant Gillespie. Submit.\u003C/p>\u003Cp>K. K. Roast. You do not have permission to access this. Okay.\u003C/p>\u003Cp>Can anybody spot the error? It is because I left off a s. We have profiles, and this is profile. So again, if I I don't know if I you can actually see the logs for anthropic. Okay.\u003C/p>\u003Cp>Yeah. We could see here's the actual logs. It's probably not showing what we've got there. But anyway alright. We'll try this one more time.\u003C/p>\u003Cp>Let's just clean this up a bit. And away we go. Dun dun dun. I don't like the looks of this, actually. Let's just reset.\u003C/p>\u003Cp>Try this again. AI response. We got the prompt. Got the profile. Dun dun dun.\u003C/p>\u003Cp>The moment of truth. Are we actually gonna be able to get this thing to work? Brig Gillespie. Submit. Obviously, this would probably be better as, like, a background job or something like that.\u003C/p>\u003Cp>Alright. So we refresh, and we have something here. Okay. Yeah. So we're getting some text back.\u003C/p>\u003Cp>It looks like we need to parse the JSON. The letter is going to be text parsed response, text, parse response dot list, and then we get metadata, which would just be the parse response, I'm assuming. Alright. We're gonna delete this out. Let's run this again.\u003C/p>\u003Cp>And hopefully I'm not burning through all these credits that I loaded up. Okay. So now we're looking great. Okay. So we have our username.\u003C/p>\u003Cp>We've got our letter. Ho ho ho. What do we have here? Another developer thinking they can impress Santa with a few measly repositories. I've seen l's with more impressive profiles.\u003C/p>\u003Cp>I got made it to the naughty list. Great. Amazing. Alright. So now that's working as intended.\u003C/p>\u003Cp>Let's let's make this pretty. Right? The form, we're going to do max width. Maybe we set this to Excel. Move that form to the somewhat in the center of the page.\u003C/p>\u003Cp>And let's just lean on AI here. Right? This is already pretty cool. One of the other things I wanna do is maybe we set up a route where we actually surface this letter. Right?\u003C/p>\u003Cp>So if we do let's do letters as a directory inside pages. And we're gonna do the username in brackets. So just take this username, make that in brackets, and then I'm gonna put letters inside here, and we'll change the name of this to the index route. Alright. So let's just clean this up a bit, wrap this up, and console the error loading.\u003C/p>\u003Cp>Actually, we could do that in finally. Great. And what we're gonna do, if the response is good, we could navigate to the username page. Cool. And that way, you know, basically, like, this could get very expensive if if you made this thing public.\u003C/p>\u003Cp>Right? You don't want people generating like 35 letters to Santa. So we can add a check to the database if we've already got that GitHub username and just return the letter that we we already have. Right? Okay.\u003C/p>\u003Cp>So on the response, as long as there's no error, we're going to navigate dot to form. Github username. And this would be await. Navigate to. Cool.\u003C/p>\u003Cp>Alright. Now let's just lean on AI and see what we could do. Add some Christmas theming to this. Let's see what this actually will do. Add some Christmas thinging, ho ho ho.\u003C/p>\u003Cp>Looks like it's generating some random messages. Code review letters to Santa, random message, decorative elements. Great. Love decorative elements. Now, with cursor, I'm just gonna click apply here.\u003C/p>\u003Cp>It should go through and run through this actual code. I can close this out and see, you know, in kind of a preview way what it's gonna change. And if we hit reload oh, what we got going on here? Letters index. Is that because I changed the route?\u003C/p>\u003Cp>Okay. Yeah. Now we're looking very festive here. This looks this looks great. AI, what can you do?\u003C/p>\u003Cp>Alright. The other thing I see, maybe we want this to be block. Will that get it done? Block. Class.\u003C/p>\u003Cp>Let's just make the width full. Width full. Okay. And then let's shrink this actual form a bit. Yeah.\u003C/p>\u003Cp>MD. There we go. Alright. We're deep in the Christmas cheer now. And, while we wait, let's well, not while we wait.\u003C/p>\u003Cp>Let's actually go in and now we're gonna work on this letter. Alright. So, this does have a Nux plug in. This is just my boilerplate where I can go in and actually request the information from Directus on the client side, or, you know, I could set up a route for this on the server side in Nuxt. Both of those ways are are totally valid depending on your application.\u003C/p>\u003Cp>Obviously, totally up to you. We will just, let's let's keep it the same theme. We're going to, like, fetch roast, or we could do, like, a roast.git.ts. And what are we gonna pass? Do we want to pass the username as a param, or we'll just pass it as a query parameter?\u003C/p>\u003Cp>Okay. So in this one, what we're gonna do, we will call the profiles endpoint inside Directus. So we'll just go const, response equals await Directus server. And, you know, sometimes you wanna make requests on the server side. That's why I've got this set up, this way.\u003C/p>\u003Cp>We're gonna do read item, and I gave this a UUID. We could've used the actual profile as the primary key. But, what we're gonna do, read profiles, and we're gonna set up a query for this. So we'll do a filter parameter, the username. So that's the field.\u003C/p>\u003Cp>We're gonna drop down again. This will be equal to the username. So first we're gonna have to get the username equals get router param. Nope. We're gonna do git query, and that would just be the query.\u003C/p>\u003Cp>Great. Username. We could destructure this if we wanted to. Return username equals username, and we're gonna return that response. Great.\u003C/p>\u003Cp>Cool. So now we do this. And on this one, what we can do is use the use fetch composable from Nuxt. So this will be we've got some data. We're gonna use fetch, and we're gonna call API slash roast.\u003C/p>\u003Cp>And the is it params? I believe. See what we got. And let's add the so the same festivities, I guess. Festiveness.\u003C/p>\u003Cp>Perfect. Alright. Decorative elements, blah blah blah, random messages. We're gonna put that up here in the script. Okay.\u003C/p>\u003Cp>Code review letters to Santa. And instead of the form, right, we're gonna replace this with data. Alright. So now if I do this, what's gonna happen? Route is not defined.\u003C/p>\u003Cp>Okay. So we just need to call use route to fetch that route. And do we actually get the stuff that we need here? We could test this API as well. Letters API roast username equals Brian Gillespie.\u003C/p>\u003Cp>Okay. Yeah. So that's getting us what we want from direct us except is it query? What is the use fetch? This is where, like, Nuxt documentation comes in handy.\u003C/p>\u003Cp>Use fetch. Where we at? We got sixteen minutes remaining. We got data use fetch. What are the URL query?\u003C/p>\u003Cp>Okay. Alias for query. That's what I thought. Root params username. Oh, data.\u003C/p>\u003Cp>Are we actually let's jump into the view dev tools. We'll hit the username route. And I see the data here. Here's the issue. Right?\u003C/p>\u003Cp>It is returning an array. So inside our routes, we could, you know, do something like this where we're just picking off the first item. I could also do that transform that on the the Nuxt side if I wanted to. Here's our letter from Santa. Cool.\u003C/p>\u003Cp>Code review letters from Santa. What I'm gonna do, let's use the pros class from Tailwind to get styling for this. We'll make the text dark green. Great. That's fine.\u003C/p>\u003Cp>And then the interior of this, we're just going to use v HTML. So we get this. Do I not have Tailwind typography into this? At plug in Tailwind typography. Okay.\u003C/p>\u003Cp>Yeah. So there we go. Now we've got the letter from Santa Claus. This is looking really nice. Perfect.\u003C/p>\u003Cp>Let's add like a cursive font. Right? Font family cursive. And this is Tailwind four, where all the config is basically CSS variables. So, really enjoying that Nuxt module, playing around with it.\u003C/p>\u003Cp>Let's find a handwritten font. Okay. Caveat. Looks nice. Nuxt has also added a a like this font amazing thing where you just throw your fonts in the CSS and it will actually download these things for you.\u003C/p>\u003Cp>So let's take a look at this. Right? I'm just gonna change this to font cursive and bada bing bada boom, we get what we want. So let's put, like, pros XL to XL. And there we go.\u003C/p>\u003Cp>So dear Bryant, what do we have here? Blah blah blah, etcetera. We have got thirteen minutes left on the clock. What can we do for fun? Let's go back and actually test this thing out.\u003C/p>\u003Cp>I'm just gonna refresh. There we go. I'm gonna do our fearless leader here at Directus, mister Ben Haines. We're gonna send this to Santa, and something bad happened. We could not find okay.\u003C/p>\u003Cp>So it looks like this one is not finding Ben's profile. Haynes, Maine. And Haynes Haynes Haynes Haynes Haynes. Would that be at, like, Master branch maybe? Where's our roast?\u003C/p>\u003Cp>Roast.post, profile read me. Try. I bet it's at master. I'm just gonna do this the quick and dirty way. Alright.\u003C/p>\u003Cp>So we go back. Let's try this again. Mister Ben Haines, we're about to roast you, sir. Alright. So we're checking the list twice.\u003C/p>\u003Cp>And eleven minutes on the clock. We've got the letter to Ben Haines from Ben Haines. Why are we not seeing the actual letter? There it is. I'm dreaming of a Nuxt application that actually works.\u003C/p>\u003Cp>What is going on with this? Letters, username, data dot name. I'm assuming because there is no name. Username. I'm running this on a sour note here.\u003C/p>\u003Cp>Ben Haines. That's kinda weird. Ben Haines. Why is it doing that? API roast username.\u003C/p>\u003Cp>What is going on here? Get async data. API roast. Why does it work for me and not for mister Haynes? What are we actually doing wrong here?\u003C/p>\u003Cp>Did I spell the name wrong? GitHub username. Alright. E pipe. Use fetch roast.\u003C/p>\u003Cp>Can't find the username? Profiles get username, get query. Is it read query? No. It's get query.\u003C/p>\u003Cp>Return query. API API roast. Ben Haines. So why aren't we why isn't this working? So it's not actually finding the username for that, which is odd because I have the username right there.\u003C/p>\u003Cp>Filter contains. Okay. I don't understand it, but we're gonna roll with it. Great. Some type of encoding or something maybe.\u003C/p>\u003Cp>Not sure. Booyah. Ben, I'm gonna read this to you. Dear Ben, ho ho ho. Well, isn't this embarrassing?\u003C/p>\u003Cp>I've been reviewing your GitHub profile, and I must say I'm thoroughly underwhelmed. 20 whole repositories, you must have been super busy this century. Meanwhile, Santa's got billions of believers worldwide. Look, I'm not saying you're on the naughty list because your contributions are lackluster. I'm saying if you were open source for coal, you barely have enough to heat a dollhouse.\u003C/p>\u003Cp>That is brutal. So let's call that a win. This is AI letters with Santa. Do we wanna do one more just for fun? Just for giggles?\u003C/p>\u003Cp>Let's let's test this out. Directus Directus. I forget Reich's actual, GitHub profile. There it is. Okay.\u003C/p>\u003Cp>So we're gonna throw mister Reich Van Zanten in there, our CTO, see what comes out of this thing. Hopefully, we got everything we need. It will do its thing. And and, that's not gonna pick on Wrike. Yeah.\u003C/p>\u003Cp>I don't know what's going on with this thing. Potentially some kind of caching issue. Don't know. Anyway, response zero. Down to the wire, five minutes, four minutes, three minutes, two minutes, no minutes.\u003C/p>\u003Cp>Is the server running? Use async data. And what if we just use fetch? Response. It's gonna be response dot letter.\u003C/p>\u003Cp>Oh, boy. Response fetch, browse, params, username. Come on. Failed to stringify the server logs. What is going on?\u003C/p>\u003Cp>This feels like a crappy way to end this one. It should be, like, festive with holiday cheer. I don't understand what is going on with Nox. Oh, duh, dummy. You have to wait the promise.\u003C/p>\u003Cp>Is that getting us what we need? Still not getting us what we need. Data is not defined on the instance. Where else am I getting the data at? Fested messages, data.\u003C/p>\u003Cp>Oh, if response. We're not even getting a response. SSR, undefined, undefined. Hey. That's the way the cookie crumbles sometimes.\u003C/p>\u003Cp>I'm not sure what I am doing wrong with this. I'm sure it'll come to me right after I get done with this. Is it like a key? Cash. Cash, no cash.\u003C/p>\u003Cp>Cache. No cache. No cache. Well, at least Ben's works. At least mine works.\u003C/p>\u003Cp>Not entirely sure what's going on with this little API that I've written, why it is caching this. But, hey, that's AI letters to Santa. That's the way it goes. This has been a hundred apps, hundred hours. Thanks for joining me.\u003C/p>\u003Cp>We'll catch you on the next episode. See you.\u003C/p>","Alright. Alright. Alright. We are back with the Christmas edition of 100 apps, one hundred hours. Today, we are going to be building AI letters from Santa. I've got my lumberjack style on today. My wife called this lumberjack Jesus earlier, but I digress. We're back for more. The rules of 100 apps, one hundred hours. If this is your first show, we have sixty minutes to plan and build an application, a website, a portal, whatever. Whatever we're building, sixty minutes, no more, no less. And rule number two, the anti rule, use whatever you have at your disposal. And since this is an AI Christmas special, I'm gonna pull out all the stops. So let's dive right in. We're gonna hit the clock here. Fire it up. Sixty minutes on the timer. Go. Alright. So AI letters from Santa. What do we actually want out of this? So I have to admit, I cheated a little bit because I thought about this with my team, and I knew we wanted to do this. I've seen things in the past where you write a letter to Santa, you get something back in the mail, etcetera, etcetera. With AI, we could take this up a notch. So combining two ideas. A while back, I saw a GitHub roast page where you enter in your GitHub profile and it, basically will scrape that and give you a roast of how well you're actually not doing in GitHub. So we're gonna combine that with a letter from Santa. And basically, what we wanna do is, enter a GitHub profile. We're gonna scrape that profile. We're going to send that to AI. So we want LLM analysis of the profile. I'm not sure what we're gonna call that. And then I'm going to bucket people on the open source naughty or nice list. So score naughty or nice list. And then we're gonna generate a letter from open source Santa. Generate a letter from open source Santa to that GitHub profile to that profile. Alright. So as far as that functionality, this looks pretty good. Right? What are the tools that we're gonna use of the trade today? I've got a Directus Docker container up and running locally. Directus is obviously the back end we're using to store all of these things. And if everything works as intended p m p m g. I guess, sometimes things don't work as you intend. I've got a Nuxt application that we are going to try and use here. I'm not sure what's going on, but let's hop into the Directus instance. So I'm just gonna pull up Chrome. We'll log in to 8055, and I should be able to pull up my back end. So great. Got Directus running. You could see this is a pretty blank instance of Directus. This is just the boilerplate I use now. There are a couple extensions installed that I was testing, just messing around with. But, let's make sure. What are we doing here? For MPMI. Sometimes these things never go as planned. Okay. So maybe now we can get this Nuxt application up and running that will be served at local host 3,000, and we'll just basically use it to scaffold out our communications. As far as what I'm using, I've upgraded this boilerplate that I've used for 100 apps to, the Nuxt UI v three alpha, just to play around with Tailwind four and, you know, some of these nice new components that are coming from, like, Radix view. So alright. Let's actually model this thing out. Right? What do we need as far as our data models? I think we just need, like, a maybe like a profiles. So under profiles, we would have, what, our username, letter from Santa, letter from Santa, list, you know, are you naughty or nice? Great. And what else do we need? Let's let's jam on that. So we'll, just set up the back end for this. I'm just gonna create a new collection. We're going to call this profiles is the name of it. And why can't I zoom way in? There we go. That's maybe too far, but all good. Let's do created at, updated at. K. Status sort not needed. Who this was created by, I'm not super concerned with. So now we have a profile. We're gonna do the username. Great. That's where we'll store the GitHub profile. What else do we need? What else did we have here? We've got the letter from Santa. What is that gonna be in letter from let's just call it letter. Great. We'll use the WYSIWYG editor inside Directus so we could just store, I'm assuming, HTML content for that. And then we've got the list. So that's basically gonna be a string. We can, you know, make this look nice inside the directus admin. We'll just give it a naughty. Feel naughty just typing that out, and then we have the nice list. Great. There we go. And I'm just going to go on record that we're probably as soon as we start typing naughty into the AI stuff, we'll probably get some some things back. Like a I probably set off the content alarms or something like that. So there we go. We've got a username. We've got a letter. We've got a list. You know, I could potentially put that in here. What I'm gonna do now, I'm just gonna let's just we're just using Directus to store this at the moment. Right? There's a lot of different ways I could go with how to actually generate the application here. But Directus allows me to create custom extensions. What I'm gonna do here is just start, working on this from the Nuxt side of it. So we're gonna input the, actual form here. Let's add a profile. What are we gonna call this? Let's just call this letters dot view. We'll get a view component set up. Lang equals TS. Great. I need to work on my little macros here. Okay. Alright. The other thing that you'll notice here is that I am using cursor. So cursor I recently started testing this thing out. Really enjoying the actual auto completions for this thing. So, I don't have it usually generate like a a giant list of code, but the automations, or the auto completions are are pretty nice for this thing. So let's start with, what, step one. It'd be enter GitHub profile info. GitHub username. Alright. So the only thing here, sometimes it gets a little wonky with the okay. So we use the you form from Nuxt UI. Good question, Brian. The new one, the alpha, they changed some of the conventions. So I've got a form with a schema. I've got a form field instead of a form group, and then I've got an input. Okay. So we've got the form, new form field, and input GitHub username. Let's just see what that gets us on the front end. We're gonna go to this page, which is letters. Okay. Alright. So let's go ahead and just center this up. I think there's actually a container component we can use. Great. Cool. Okay. So now we have a GitHub username, and let's add a submit button. New button, click handle submit, and boom. We have a GitHub username, blah blah blah. Hit submit. Supposedly does something. What it's gonna do right now? Absolutely nothing. Alright. So the next thing that we wanna do, let's kick this thing off. We want to have the form state, we use reactive for that. Great. GitHub username. Okay. And then we're gonna write a function to handle submit. Thank you. Yeah. Great. We'll just, console log that. Right? Boom. There we go. We can see the GitHub username, yada yada yada. Alright. This is actually gonna be an async function. Great. Okay. So now what do we wanna do with this? Right? We have to think about our application structure. And what I'm gonna do here is just basically add a Nuxt server route. So if we break this down, in this server route, what we're gonna do, call the GitHub API, call GitHub API. We're gonna wanna grab a couple pieces of information like the user profile, or any other repos, and maybe, like, their their public read me. I guess we could loop through the actual repos and, you know, pick up more information there, but, let's see what we can get done with that piece. Alright. So let's just go here. We're gonna set up a new route. Let's call it, roast route. We'll do post, and just gonna copy the event handler here. K. So now whenever we hit this route with a post, it should return hello world. We can just check and see if that's actually gonna work. So, we will do the I'm trying to think if that's gonna return. Nope. So we got the response. We're gonna do await. We can use the regular fetch or the dollar sign fetch, which is the Nuxt specific version. And just test this out, see what we get back in the console. Where are you? Okay. Yeah. So we can see the request going out. We can see hello world coming back. Great. Cool. Alright. So now what we're gonna do, right, let's just scaffold this out. We are going to pick up the body. There's a wait read body. Great. So that is going to have the GitHub username in there. And, how did we spell that? Yep. Great. Alright. So we're gonna say GitHub username, and then we've got, like, this git roast function. I'm not really sure where some of these auto completions are coming from. But, what we're gonna do next, let's call yep. There we go. That's a good one. API users, GitHub username. Is that the correct one? Let's just test that. All the developers on my team are screaming and crying at the moment, watching all these AI auto completions. So that seems fair. Great. And let's actually use the Nuxt equivalent. Just this is using OFETCH, which does some automated data transformation and should automatically throw errors for you as well, which is nice. So this is gonna be the let's call this a profile. Alright. And then if we take a look at the profile, we probably wanna get the actual repos for that user as well. Alright. So we'll get the repos. Great. And let's just take a look at the data we're getting from the actual repos. Okay. So what do we actually concern ourself with here? Do we actually want all of this information? What do we actually care about from these? So stargazers, watchers counts, maybe those properties. You know what? Let's just jam it all in there and see what comes out of it. Right? And then let's get the profile readme. GitHub user content, GitHub username, repos dot name, Repos dot name. No. That's not gonna cut it. I think it's gonna be, what, GitHub username. GitHub username. Somebody who's already done this before. Give me the structure. And then main. Let's just see if we can find that. Read me will just populate my name. I don't even know if I have a actually have a read me. Yeah. There we go. Okay. So that is the structure. Great. That is what we needed to confirm. And now let's just actually return this and see what we'll get back. Alright. Brian Gillespie. Now I'm gonna fire this away. Roast. No. Nothing found. Well, that's a little concerning. GitHub username equals body. Read the body. We have fetched the user's GitHub username. Let's just console log the username. API dot GitHub users. I don't see the actual username coming back. GitHub. That's always fun. Alright. What did I do wrong? GitHub underscore username. Okay. Oh, duh. Are we actually passing that in the body of the form? Form. Console dot log response. Request payload. API slash roast dot post. What are we getting back here? GitHub username form dot GitHub username. Oh, that's right. We are missing a state variable here. So is it actually submitting the form? No? No. Okay, friends. What do we do from here? We have handle submit. We're going to use fetch await. Wait that fetch request. We should have already got this back. Of course. There it is. What a dumb dumb. Forgot to actually fix the v model there. So that's what that is. Sometimes, these are not great to do at the end of the day. But okay. Where we at as far as time? We've got forty two minutes remaining. I feel pretty confident on this one. Alright. Now with our roast, we can remove this. We should be able to get that information. Now let's make sure that we're getting what we want back from that API. Great. There's the profile. There's the repos, and there's the profile. Read me. Great. Alright. So what are we gonna do with these now? Right? The next step in this process would be to, pass the profile to LLM and ask it to summarize for us. What do we want this to return? It should return something that looks like this. We want a letter from Santa. Letter from Santa in HTML. And then we're gonna want the the list, naughty or nice. And that should be all we really need to return. Alright. LLM returns JSON. Cool. So what is the LLM we're gonna use? You know, typically, I use OpenAI for a lot of the stuff that I do here at Directus. I've been messing around a lot with, Claude locally. So we're just going to try this out. Santa letters. So we're gonna use anthropic. There's my API key. We're going to drop that in our ENV file, if I can actually get there. Jeez. There we go. I'm just gonna call it Claude ABI key. Okay. Great. And by the time you've watched this, hopefully, I've disabled that key. So, don't stop the video and try to figure that out. Alright. Let's pull up our docs for the API. We need to get the API reference. And let's define this prompt. Prompt. You are a letter writing AI. Alright. Analyze the following GitHub profile. You are the open source Santa Claus. You determine whose open source contributions are naughty or nice, analyze the following GitHub profile, Return a JSON object with the following fields, a letter from Santa and HTML. Set a really high bar for the nice list. What else do we need as far as a prompt? And, yeah, here is the data, profile, readme, json, stringify. Wonder why it's doing that. But okay. Nevertheless, there we go. Turn a JSON object instead of really write the letter in a snarky sarcastic tone. Cool. Alright. And now we're going to send that to Anthropic. Alright. So if we look at their oh, looks like we could just use their JavaScript SDK. That's great. Let's go ahead and open this up. We'll fire that up, install this thing. Import anthropic. Great. And then we're going to create that message. Alright. Constant AI response equals anthropic messages dot create Claude Sonnet. Okay. Messages user role, content prompt. Do we wanna set, like, max tokens? What is the what's the default for max tokens? Where do we actually pass this API key? Getting started authentication, x API key. API key equals process e n v. And, again, like, you could start to see why I really like using cursor because it has, like, this sixth sense for a lot of this stuff that I'm actually trying to do. Sometimes it gets that wrong, but a lot of times it gets it right. So alright, AI response messages. Do we wanna set a max tokens? Body messages, max tokens required. Let's give some more parameters. Write a short letter in a short in a snarky sarcastic tone. That is 500 words or less. And then for the tokens, if we look at Sonnet, we've got like a context window of like 200,000, so maybe a hundred thousand tokens. Oh, no. That's the output. Max output is eight nine one two. That's fine. Max tokens. Great. And let's return. Actually, what we're gonna do next is save that to the Directus database. Right? So we've got this collection for our profile. What I've also done, I've got a utility set up here. This is just using the Directus SDK. And one of the nice things about Nuxt, I say that a lot, is, the ability to it will auto import this for me. So I don't have to import it. I should just be able to call Directus server right here. So let's call it Directus response equals await directus server dot request create item. That's going to be in the profile. And we'll do the GitHub username. That's actually going to be username. Letter response, content dot text. I don't actually know what we're gonna get back directly. Return only a JSON object. And maybe we wanna add something like this for let's just do code. We'll set this up. And I'm just gonna add a field for, let's call it metadata or something where I'm just gonna store the entire response. And honestly, let's just do that to begin with. Metadata, AI response, content dot text. So if we take a look at the API reference, we go back to messages here. I'm kinda curious as to what we're gonna get back. The content text. Okay. Type text something. We'll get back something from the system. Let's just even do it this way. We'll say content direct us response, and then we're going to return direct us response. See what that gives us. Now let's go in. Where's our app? We'll switch back to Chrome. I do like Arc. I've found it to be lacking for development because it's just not super fast. Alright. So fingers crossed that this actually does what it should do. And, let's make this even nicer. And we'll add, like, a loading state, constant loading, ref equals false. We'll add loading dot value equals true. Loading dot value equals false. Great. And what else do we want to do? Is there a loading state on the actual form? Let's take a look. So Nuxt UI state, there is not a loading state on that. There should be on the button though. So just update that. Okay. And let's test this bad way out. Submit. Alright. We're waiting. We're waiting. We're waiting. We're waiting. We're waiting. This could take a minute. So, you know, we might even want to, like, potentially set up a oh, okay. So we're not getting anything back. We see a request error. So let's go into our roast, and we should probably do some error handling. Alright. Catch error, console error. Return, or we could just throw the error. What do we got here? Format. Alright. Let's refresh. I'll try this again and see what kind of error we're getting and why. Pending. Invalid user credentials for Directus. Okay. Great. So, just wasting tokens there, throwing them into the void. One of the things that you'll notice, I do have this direct as URL set up, but, my server token is probably a % not correct. So I'm gonna go in and create a token for this. We'll just create a new token. We'll call this the server token. And I wanna make sure in my utility that I have that set as server token, direct us URL. Okay. Let's try this thing again. PPM dev. I will restart the dev server, pull in that new ENV, though I think Nuxt may automatically update that for us. How we doing on time? We got twenty nine minutes left, so I'm feeling pretty confident that we can get something out of this. Let's go ahead and try it again. Bryant Gillespie. Submit. K. K. Roast. You do not have permission to access this. Okay. Can anybody spot the error? It is because I left off a s. We have profiles, and this is profile. So again, if I I don't know if I you can actually see the logs for anthropic. Okay. Yeah. We could see here's the actual logs. It's probably not showing what we've got there. But anyway alright. We'll try this one more time. Let's just clean this up a bit. And away we go. Dun dun dun. I don't like the looks of this, actually. Let's just reset. Try this again. AI response. We got the prompt. Got the profile. Dun dun dun. The moment of truth. Are we actually gonna be able to get this thing to work? Brig Gillespie. Submit. Obviously, this would probably be better as, like, a background job or something like that. Alright. So we refresh, and we have something here. Okay. Yeah. So we're getting some text back. It looks like we need to parse the JSON. The letter is going to be text parsed response, text, parse response dot list, and then we get metadata, which would just be the parse response, I'm assuming. Alright. We're gonna delete this out. Let's run this again. And hopefully I'm not burning through all these credits that I loaded up. Okay. So now we're looking great. Okay. So we have our username. We've got our letter. Ho ho ho. What do we have here? Another developer thinking they can impress Santa with a few measly repositories. I've seen l's with more impressive profiles. I got made it to the naughty list. Great. Amazing. Alright. So now that's working as intended. Let's let's make this pretty. Right? The form, we're going to do max width. Maybe we set this to Excel. Move that form to the somewhat in the center of the page. And let's just lean on AI here. Right? This is already pretty cool. One of the other things I wanna do is maybe we set up a route where we actually surface this letter. Right? So if we do let's do letters as a directory inside pages. And we're gonna do the username in brackets. So just take this username, make that in brackets, and then I'm gonna put letters inside here, and we'll change the name of this to the index route. Alright. So let's just clean this up a bit, wrap this up, and console the error loading. Actually, we could do that in finally. Great. And what we're gonna do, if the response is good, we could navigate to the username page. Cool. And that way, you know, basically, like, this could get very expensive if if you made this thing public. Right? You don't want people generating like 35 letters to Santa. So we can add a check to the database if we've already got that GitHub username and just return the letter that we we already have. Right? Okay. So on the response, as long as there's no error, we're going to navigate dot to form. Github username. And this would be await. Navigate to. Cool. Alright. Now let's just lean on AI and see what we could do. Add some Christmas theming to this. Let's see what this actually will do. Add some Christmas thinging, ho ho ho. Looks like it's generating some random messages. Code review letters to Santa, random message, decorative elements. Great. Love decorative elements. Now, with cursor, I'm just gonna click apply here. It should go through and run through this actual code. I can close this out and see, you know, in kind of a preview way what it's gonna change. And if we hit reload oh, what we got going on here? Letters index. Is that because I changed the route? Okay. Yeah. Now we're looking very festive here. This looks this looks great. AI, what can you do? Alright. The other thing I see, maybe we want this to be block. Will that get it done? Block. Class. Let's just make the width full. Width full. Okay. And then let's shrink this actual form a bit. Yeah. MD. There we go. Alright. We're deep in the Christmas cheer now. And, while we wait, let's well, not while we wait. Let's actually go in and now we're gonna work on this letter. Alright. So, this does have a Nux plug in. This is just my boilerplate where I can go in and actually request the information from Directus on the client side, or, you know, I could set up a route for this on the server side in Nuxt. Both of those ways are are totally valid depending on your application. Obviously, totally up to you. We will just, let's let's keep it the same theme. We're going to, like, fetch roast, or we could do, like, a roast.git.ts. And what are we gonna pass? Do we want to pass the username as a param, or we'll just pass it as a query parameter? Okay. So in this one, what we're gonna do, we will call the profiles endpoint inside Directus. So we'll just go const, response equals await Directus server. And, you know, sometimes you wanna make requests on the server side. That's why I've got this set up, this way. We're gonna do read item, and I gave this a UUID. We could've used the actual profile as the primary key. But, what we're gonna do, read profiles, and we're gonna set up a query for this. So we'll do a filter parameter, the username. So that's the field. We're gonna drop down again. This will be equal to the username. So first we're gonna have to get the username equals get router param. Nope. We're gonna do git query, and that would just be the query. Great. Username. We could destructure this if we wanted to. Return username equals username, and we're gonna return that response. Great. Cool. So now we do this. And on this one, what we can do is use the use fetch composable from Nuxt. So this will be we've got some data. We're gonna use fetch, and we're gonna call API slash roast. And the is it params? I believe. See what we got. And let's add the so the same festivities, I guess. Festiveness. Perfect. Alright. Decorative elements, blah blah blah, random messages. We're gonna put that up here in the script. Okay. Code review letters to Santa. And instead of the form, right, we're gonna replace this with data. Alright. So now if I do this, what's gonna happen? Route is not defined. Okay. So we just need to call use route to fetch that route. And do we actually get the stuff that we need here? We could test this API as well. Letters API roast username equals Brian Gillespie. Okay. Yeah. So that's getting us what we want from direct us except is it query? What is the use fetch? This is where, like, Nuxt documentation comes in handy. Use fetch. Where we at? We got sixteen minutes remaining. We got data use fetch. What are the URL query? Okay. Alias for query. That's what I thought. Root params username. Oh, data. Are we actually let's jump into the view dev tools. We'll hit the username route. And I see the data here. Here's the issue. Right? It is returning an array. So inside our routes, we could, you know, do something like this where we're just picking off the first item. I could also do that transform that on the the Nuxt side if I wanted to. Here's our letter from Santa. Cool. Code review letters from Santa. What I'm gonna do, let's use the pros class from Tailwind to get styling for this. We'll make the text dark green. Great. That's fine. And then the interior of this, we're just going to use v HTML. So we get this. Do I not have Tailwind typography into this? At plug in Tailwind typography. Okay. Yeah. So there we go. Now we've got the letter from Santa Claus. This is looking really nice. Perfect. Let's add like a cursive font. Right? Font family cursive. And this is Tailwind four, where all the config is basically CSS variables. So, really enjoying that Nuxt module, playing around with it. Let's find a handwritten font. Okay. Caveat. Looks nice. Nuxt has also added a a like this font amazing thing where you just throw your fonts in the CSS and it will actually download these things for you. So let's take a look at this. Right? I'm just gonna change this to font cursive and bada bing bada boom, we get what we want. So let's put, like, pros XL to XL. And there we go. So dear Bryant, what do we have here? Blah blah blah, etcetera. We have got thirteen minutes left on the clock. What can we do for fun? Let's go back and actually test this thing out. I'm just gonna refresh. There we go. I'm gonna do our fearless leader here at Directus, mister Ben Haines. We're gonna send this to Santa, and something bad happened. We could not find okay. So it looks like this one is not finding Ben's profile. Haynes, Maine. And Haynes Haynes Haynes Haynes Haynes. Would that be at, like, Master branch maybe? Where's our roast? Roast.post, profile read me. Try. I bet it's at master. I'm just gonna do this the quick and dirty way. Alright. So we go back. Let's try this again. Mister Ben Haines, we're about to roast you, sir. Alright. So we're checking the list twice. And eleven minutes on the clock. We've got the letter to Ben Haines from Ben Haines. Why are we not seeing the actual letter? There it is. I'm dreaming of a Nuxt application that actually works. What is going on with this? Letters, username, data dot name. I'm assuming because there is no name. Username. I'm running this on a sour note here. Ben Haines. That's kinda weird. Ben Haines. Why is it doing that? API roast username. What is going on here? Get async data. API roast. Why does it work for me and not for mister Haynes? What are we actually doing wrong here? Did I spell the name wrong? GitHub username. Alright. E pipe. Use fetch roast. Can't find the username? Profiles get username, get query. Is it read query? No. It's get query. Return query. API API roast. Ben Haines. So why aren't we why isn't this working? So it's not actually finding the username for that, which is odd because I have the username right there. Filter contains. Okay. I don't understand it, but we're gonna roll with it. Great. Some type of encoding or something maybe. Not sure. Booyah. Ben, I'm gonna read this to you. Dear Ben, ho ho ho. Well, isn't this embarrassing? I've been reviewing your GitHub profile, and I must say I'm thoroughly underwhelmed. 20 whole repositories, you must have been super busy this century. Meanwhile, Santa's got billions of believers worldwide. Look, I'm not saying you're on the naughty list because your contributions are lackluster. I'm saying if you were open source for coal, you barely have enough to heat a dollhouse. That is brutal. So let's call that a win. This is AI letters with Santa. Do we wanna do one more just for fun? Just for giggles? Let's let's test this out. Directus Directus. I forget Reich's actual, GitHub profile. There it is. Okay. So we're gonna throw mister Reich Van Zanten in there, our CTO, see what comes out of this thing. Hopefully, we got everything we need. It will do its thing. And and, that's not gonna pick on Wrike. Yeah. I don't know what's going on with this thing. Potentially some kind of caching issue. Don't know. Anyway, response zero. Down to the wire, five minutes, four minutes, three minutes, two minutes, no minutes. Is the server running? Use async data. And what if we just use fetch? Response. It's gonna be response dot letter. Oh, boy. Response fetch, browse, params, username. Come on. Failed to stringify the server logs. What is going on? This feels like a crappy way to end this one. It should be, like, festive with holiday cheer. I don't understand what is going on with Nox. Oh, duh, dummy. You have to wait the promise. Is that getting us what we need? Still not getting us what we need. Data is not defined on the instance. Where else am I getting the data at? Fested messages, data. Oh, if response. We're not even getting a response. SSR, undefined, undefined. Hey. That's the way the cookie crumbles sometimes. I'm not sure what I am doing wrong with this. I'm sure it'll come to me right after I get done with this. Is it like a key? Cash. Cash, no cash. Cache. No cache. No cache. Well, at least Ben's works. At least mine works. Not entirely sure what's going on with this little API that I've written, why it is caching this. But, hey, that's AI letters to Santa. That's the way it goes. This has been a hundred apps, hundred hours. Thanks for joining me. We'll catch you on the next episode. See you.","ec46771b-85fb-4967-9a7e-81102f96cf74",[184],"cc9262db-1ae2-4bc1-a1f9-7d2fc51a396d",[],{"reps":187},[188,244],{"name":189,"sdr":8,"link":190,"countries":191,"states":193},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[192],"United States",[194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243],"Michigan","Indiana","Ohio","West Virginia","Kentucky","Virginia","Tennessee","North Carolina","South Carolina","Georgia","Florida","Alabama","Mississippi","New York","MI","IN","OH","WV","KY","VA","TN","NC","SC","GA","FL","AL","MS","NY","Connecticut","CT","Delaware","DE","Maine","ME","Maryland","MD","Massachusetts","MA","New Hampshire","NH","New Jersey","NJ","Pennsylvania","PA","Rhode Island","RI","Vermont","VT","Washington DC","DC",{"name":245,"link":246,"countries":247},"Michelle Riber","https://meetings.hubspot.com/mriber",[248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,225,436,437],"Albania","ALB","Algeria","DZA","Andorra","AND","Angola","AGO","Austria","AUT","Belgium","BEL","Benin","BEN","Bosnia and Herzegovina","BIH","Botswana","BWA","Bulgaria","BGR","Burkina Faso","BFA","Burundi","BDI","Cameroon","CMR","Cape Verde","CPV","Central African Republic","CAF","Chad","TCD","Comoros","COM","Côte d'Ivoire","CIV","Croatia","HRV","Czech Republic","CZE","Democratic Republic of Congo","COD","Denmark","DNK","Djibouti","DJI","Egypt","EGY","Equatorial Guinea","GNQ","Eritrea","ERI","Estonia","EST","Eswatini","SWZ","Ethiopia","ETH","Finland","FIN","France","FRA","Gabon","GAB","Gambia","GMB","Ghana","GHA","Greece","GRC","Guinea","GIN","Guinea-Bissau","GNB","Hungary","HUN","Iceland","ISL","Ireland","IRL","Italy","ITA","Kenya","KEN","Latvia","LVA","Lesotho","LSO","Liberia","LBR","Libya","LBY","Liechtenstein","LIE","Lithuania","LTU","Luxembourg","LUX","Madagascar","MDG","Malawi","MWI","Mali","MLI","Malta","MLT","Mauritania","MRT","Mauritius","MUS","Moldova","MDA","Monaco","MCO","Montenegro","MNE","Morocco","MAR","Mozambique","MOZ","Namibia","NAM","Niger","NER","Nigeria","NGA","North Macedonia","MKD","Norway","NOR","Poland","POL","Portugal","PRT","Republic of Congo","COG","Romania","ROU","Rwanda","RWA","San Marino","SMR","São Tomé and Príncipe","STP","Senegal","SEN","Serbia","SRB","Seychelles","SYC","Sierra Leone","SLE","Slovakia","SVK","Slovenia","SVN","Somalia","SOM","South Africa","ZAF","South Sudan","SSD","Spain","ESP","Sudan","SDN","Sweden","SWE","Tanzania","TZA","Togo","TGO","Tunisia","TUN","Uganda","UGA","United Kingdom","GBR","Vatican City","VAT","Zambia","ZMB","Zimbabwe","ZWE","UK","Germany","Netherlands","Switzerland","CH","NL",1773850431375]