[{"data":1,"prerenderedAt":437},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-social-media-platform":121,"100-apps-100-hours-social-media-platform-next":168,"sales-reps":185},{"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":167},"385bdd7d-038d-4f9c-8037-357e5272420a","social-media-platform","959732000","It’s social time. Join Bryant as he races to build a social media platform with registration, content feed and the ability to dish out likes and follows in just 60 minutes.","9aa4373b-1377-445d-acd3-e473766e6fd0",62,9,"2024-06-28","Mission: Social Media Platform","\u003Cp>Speaker 0: Alright. Alright. Alright. Welcome back to yet another episode of 100 Apps, 100 Hours. I'm your host Brian Gillespie, developer advocate here at Directus.\u003C/p>\n\u003Cp>And I got a doozy today. It's one, honestly, I've not been looking forward to. So if you're new to the show, basically, we rebuild or try to build some of your favorite app ideas in 60 minutes or less, or publicly fail trying. I fail a lot. But that's okay.\u003C/p>\n\u003Cp>We learn something each time. There are only two rules to this. You have 60 minutes to plan and build, which always ends up not being enough time. And rule number 2 is use whatever you have at your disposal, kind of the anti rule. So with that, let's fire into this episode, the social media platform.\u003C/p>\n\u003Cp>Again, I like I said, I've not been looking forward to this. What are we gonna be building? We're gonna try to build something similar to Twitter. Where we've got a feed, post from different people that we're gonna follow on social. And, that is what it is.\u003C/p>\n\u003Cp>So, again, it should be an interesting episode. Let's get started on the clock. Start the timer and away we go. Right? So when I look at social media platform like Twitter, you've got a list of posts, you've got users that are on the service, You can follow people.\u003C/p>\n\u003Cp>You can favorite their posts. But as far as the functionality we're really looking for out of this, let's define it right when we go to plan here. We're going to, register users, which I've already got set up, inside my example project that I use all the time in these shows. We want to follow other users, create a feed of chirps. We won't call them tweet.\u003C/p>\n\u003Cp>We'll call them chirps. Sounds great. Let's shrink this down. This is gonna be our functionality needed. Alright.\u003C/p>\n\u003Cp>That seems like a pretty good functionality set for this. Again, yeah, I can't stress how much fun I'm looking forward to having on this episode. Alright. Now as far as our data model, you know, when you kind of break it down, there's the obvious ones like users. Right?\u003C/p>\n\u003Cp>So if I do some diagramming here, we have our users. These are actually gonna come from our directus users collection that directus gives us out of the box. That gives us auth and permissions and all the fancy stuff that we're really interested in. So we'll have Directus users and then we've got the chirps. As far as the chirp, it probably has, what, like some content.\u003C/p>\n\u003Cp>Maybe there's an image or a video. You know, this could just be a file if we wanted to share that. And then a timestamp or, created at date. Right? Those are our chirps.\u003C/p>\n\u003Cp>Those have a relationship back to the user. There's a user that chirped, so we can represent that. And then, what else are we gonna have here? We're gonna have some followers. Alright.\u003C/p>\n\u003Cp>So, basically the the users are gonna follow themselves, but because that's a many to many relationship, we're gonna need like a separate table for this. So when we talk about this, we're gonna have a follower follower ID. So this is I am the follower. The followee. I always hate naming stuff.\u003C/p>\n\u003Cp>Followee ID, followedid. So again those are gonna be a two way relationship with users. And then, what else do we have? Right? The other part that I'm not a 100% sure about, we've got this feed that is unique for each user.\u003C/p>\n\u003Cp>Right? So to me that means a separate collection. So we have feeds, we've got a user that's associated to the feed. And then what else do we have? Right?\u003C/p>\n\u003Cp>We're gonna have followers, feeds. The feed is gonna be a or like updated at. When was the feed last updated? And trying to think of, like, the other paradigm here. We're gonna just have a, like, the actual tweets that are or chirps that are in that feed.\u003C/p>\n\u003Cp>Alright. And then that means we need, like, a junction table as well, feed chirps. So this will be the chirp and the feed. Alright. So I I think this is the the data model that I wanna go with for this.\u003C/p>\n\u003Cp>We'll see how this actually plays out. As far as what I've got set up, right, is the standard config that I use for all these episodes, if you've ever caught one of the past episodes. I've just got a little Nuxt starter application that has a a couple of routes, like a register and a login. Let me just clear my cookies so we can see what those look like. Right?\u003C/p>\n\u003Cp>Just a register and login route. I've got a Directus SDK plugin configured here. So we just create a REST client. There's also a real time client in case I decide that we need to use real time in this. And then on the other side of it, I've got a blank direct assistance, just a single admin user.\u003C/p>\n\u003Cp>So, alright. Let's get to work on our data model. That's what we're gonna bang out first. And like I said, we've already got direct us users here. Let's work on our chirps.\u003C/p>\n\u003Cp>So that will be the first collection that we create, and I could zoom way in inside the Directus instance here. And do I really am I concerned with the primary key field for these being UUID or auto incremented integer? You know, typically, I'm choosing UID, but in in this case, you know, maybe integer is is fine really. For created on, we're gonna do created at. And created by, we're just gonna do the user I'm gonna call that user ID.\u003C/p>\n\u003Cp>So whatever user creates this chirp, that'll be saved in the database. And then whenever they create this chirp, will be saved as well. Once the chirp is out there, it is gone. We are not going to allow editing or anything like that. And as far as our content, maybe we want to support markdown for this.\u003C/p>\n\u003Cp>Message content. Chirp content. Sounds good. And then, you know, let's get wild with it and maybe even support a file. You can upload a single file to your Chirp to share.\u003C/p>\n\u003Cp>Alright, so that's our model. I'm gonna unhide these other fields here just so I could see these when I look at them. And next I want to I'm just gonna chirp. Chirp chirp. Save it.\u003C/p>\n\u003Cp>And you'll see that saves the admin user and the date, and now we've got our first chirp for this social media platform. I don't know what we're gonna call this. Why? Why not? I think why is the example that, our CTO, Reich Van Zanten, used in one of his talks.\u003C/p>\n\u003Cp>So, alright. What's next? We're gonna do our followers. Right? So we have our followers.\u003C/p>\n\u003Cp>And in this case, we have got, really not concerned with any of those other fields. What we're gonna do, I want to go into my data model. I'm I'm gonna go to our system collection, right? And we're gonna do a Directus users, and we're gonna go in and create a many to many relationship here. It's gonna be followers.\u003C/p>\n\u003Cp>We're gonna go to the followers table. And what do we have here? We've got the directus user. This is the follower followee ID. This will be the follower ID, I think.\u003C/p>\n\u003Cp>Alright. Is that gonna work out? We got followers. We're good. And then we could add, oh, that's our where do we have that?\u003C/p>\n\u003Cp>Follow words collection. Yeah. We don't need to add that. Right? Great.\u003C/p>\n\u003Cp>And then if we delete any of these other items, we're basically just going to like if somebody if a user gets deleted or a follower gets deleted, we're just going to remove those. So okay. I hope that's gonna give us what we're looking for. So now we've got a followers table and junction direct us users followers. Oh, okay.\u003C/p>\n\u003Cp>So it's created that extra table for me. Why did it do that? Because I have a relationship there? That's some unexpected behavior, but that'd be okay. We'll roll with that.\u003C/p>\n\u003Cp>So on our users, this table is pretty much worthless, we'll just remove that. Unexpected error. Okay, fun stuff as always. Let's let's try this over again, right? We've got followers, we're just gonna delete that.\u003C/p>\n\u003Cp>Now that table is removed. We'll do another mini to mini. And we'll call it followers. And actually the related collection here we want is direct us users and not followers. So that's where I goofed up originally.\u003C/p>\n\u003Cp>And we're gonna call this followers. That'll be our new collection. We got the followee ID and then the follower ID. And I think this will get us what we want. So this will be followees.\u003C/p>\n\u003Cp>Alright. Let's try that. See if that gets us what we want. So now we have followers and followees. And then we have a table called followers that we can inspect and make sure everything is looking correct.\u003C/p>\n\u003Cp>Alright, great. So we've got that set. Now we're gonna work on our feeds, right? Whenever I want to, I like check my feed, I I need some way to store the posts that are going into that specific feed. So we'll have a feeds collection.\u003C/p>\n\u003Cp>Again this could be auto incremented. And for the feed side of it, basically we're gonna have a when was this feed updated? Let's do updated at. Just carry that same one. I I really love these optional system fields inside Directus.\u003C/p>\n\u003Cp>Again, it just gives you some of that functionality that you need like time stamps and status and things like that baked right out of the gate. Alright. So we've got a feed. We've got updated at. We've got a we're gonna have to add a user to this feed.\u003C/p>\n\u003Cp>Right? So this is a user that's gonna be a mini to 1 relationship to the user's collection, direct to users. And then if I open up the advanced settings for for any of these fields that I'm creating, I could go in and create the corresponding field. Right? So I can add additional feeds for this specific user.\u003C/p>\n\u003Cp>You know, maybe we wanted to subscribe to different topics or something like that. So on delete of directus users, we wanna delete the feeds item because there's no need for a feed for a user that is not around, right? And when it comes to our display template, I can also go in and control what this will actually display here as well. Just a nice thing if you're doing a lot of editing inside the Directus Data Studio. So we got the updated at.\u003C/p>\n\u003Cp>There's the user. And then we're gonna need another relationship between the chirps and the feed, right? So we need to know what are all the chirps within that specific feed. So, how are we gonna do this? That'll be a many to many relationship again.\u003C/p>\n\u003Cp>Because a one chirp could be in many different feeds. So we'll go here inside Directus, we'll just search for the many to many relationship, right? And we are currently where? We're on feeds. So this will be our chirps.\u003C/p>\n\u003Cp>We'll add chirps as the related collection here, we'll show a link. And I'm gonna go into the relationship under advanced settings in case I want to add this, right? This is the feed ID, this is the chirp ID. Sounds great. Looks good.\u003C/p>\n\u003Cp>And then we can add the inverse relationship that we want. So we could look at a chirp and see all the feeds that it's involved in. Not sure we necessarily need that either. As far as the sort field, we're just gonna sort by the the timing of the specific chirp. Alright.\u003C/p>\n\u003Cp>So I with that, I think we've got our data model, like, pretty fleshed out here. We got the users already. We got chirps. We got followers. We got feeds.\u003C/p>\n\u003Cp>Cool. As a user, I could go in and I could follow someone, followee, except I don't have any actual users to follow at this point, right? So if we turn our attention to the front end of the project, I can go ahead and register a new user, Right. Let's call this edonbusk@example.com. And we'll give this a very secure exam model.\u003C/p>\n\u003Cp>Okay. Alright. So if we go to log in now, we should see we're already logged in. Great. Do I see a user here?\u003C/p>\n\u003Cp>I do. Elon Busk. Should've added a first name and last name to the actual registration form. Cool. So now I've got users that I can actually follow inside this.\u003C/p>\n\u003Cp>So if I go into my admin user, I should be able to add Elon Busk as one of my followees. Man, that terminology is gonna trip me up the whole time. I should have done something else. Following or something like that. Alright, so now, we don't have a feed for this.\u003C/p>\n\u003Cp>So one of the things that we could do inside Directus, whenever a a new user is created, you probably want to add a feed for them, right? So a couple ways you could go about that. One way is just flows, right? So flows are automations inside Directus that we can create. And I could trigger these based on a number of different actions.\u003C/p>\n\u003Cp>So we're just gonna do a create profile or create feed for new users. Okay. As far as a trigger setup we're going to do an event hook. And the action we want to be is non blocking. And should be whenever we is there a way to do it?\u003C/p>\n\u003Cp>Whenever a user is created auth.create. Auth.update. So whenever items are created and I just am going to do the Directus users collection here. So we're gonna trigger this whenever a new user is created, right? If I go in and I'll just throw these up side by side.\u003C/p>\n\u003Cp>Right? I'm already logged in. Let me just destroy this. Now we'll register a new user, new user at example.com. New password.\u003C/p>\n\u003Cp>User has been created. If I refresh this we could see that. Great. We can see there's the user, there's the key that we're interested in. Alright.\u003C/p>\n\u003Cp>And now I'm just gonna go in and create a feed for this, create feed. Alright, so there's our feed. And as far as the feed data model, I'm just gonna duplicate this because I do not remember what that looked like. Hazard of the gig. Right?\u003C/p>\n\u003Cp>So if I look at the feed, we've got a user and we've got chirps. So we'll just have one param that we're gonna pass. And we could even add like a a system message if we wanted to. But for now let's just create the feed. So we'll add an operation for it, we're gonna create data, we're gonna create a feed.\u003C/p>\n\u003Cp>And then inside the payload we've got the user key. And then we're gonna dynamically pull information from the trigger for this. So trigger dot key. And we'll just use that mustache syntax. And this should get us what we want.\u003C/p>\n\u003Cp>We'll do full access. Let me just check that one more time. Trigger dot key. Okay, great. Save.\u003C/p>\n\u003Cp>And now let's try this again. Right? I see 0 feeds in here. I'm just going to remove my session token. And we'll try john@example.com.\u003C/p>\n\u003Cp>Password. So we should have a John and example here. And we can see we've got a feed for that user now as well. Great. Perfect.\u003C/p>\n\u003Cp>Alright, so if we go back to our functionality that we need, we can register users, right. Do we have the ability to follow other users? In this case, yes, we do. We don't have it wired up to the front end yet. But let's let's stay focused on Directus, right?\u003C/p>\n\u003Cp>If I am following someone, and this is not showing up really nicely. So let's just edit the interface for this. We are going to show the avatar thumbnail with a first name, last name. And I can just copy this display template here and I can do that same thing here. So the the difference between the interface and the display, the interface shows up on the form itself.\u003C/p>\n\u003Cp>So when you're in that detail view, the display shows up on the layouts. So if I'm here looking at a list of followers, this is the display and this is the actual interface, right. So let's go back into our followers and we'll we'll fix this one as well. Should just be able to copy paste the same thing. Great.\u003C/p>\n\u003Cp>Cool. Cool. There it is. We got our followers, followees. Now if I am following Elon Musk, great name by the way, anytime he chirps it should populate into my feed, right?\u003C/p>\n\u003Cp>So, if I look at my feed, I don't have a feed yet, So let's create a feed for me. And here's, here's my feed. I can't really tell which one that is, so we'll just fix that really quickly as well. We'll show a display the direct us user. Great.\u003C/p>\n\u003Cp>Cool. Alright. So now I can see there's my feed, there's John's feed. Anytime Elon Dusk tweets, I want to populate that into, a feed. Right?\u003C/p>\n\u003Cp>Or let's say if I go into John's user, for example, John Doe, maybe John Doe is following me. So if we add me as an admin user, that'll be a good example. Right? If I chirp, chirp again. Right?\u003C/p>\n\u003Cp>This is, again, this is just writing it to the SQL database. Direct is is a is a nice complement to that. Like whatever changes I'm making here being mirrored. But, I chirp. If I go to the feed, nothing happens for John Doe.\u003C/p>\n\u003Cp>Right? So whenever a new chirp comes in, I keep wanting to call it a tweet, I want to basically populate that feed for all the other users that are, following that user, right? So again, I could reach for flows on this, populate feeds. Now at the scale of something like x or or Twitter or Facebook, this is going to probably break really quickly. Because, you know, you could have millions of followers and, you're gonna want to look at other solutions to scale this.\u003C/p>\n\u003Cp>But again, this is what we can build in an hour, so let's do that. So whenever an item is created, a chirp, we're gonna do some logic on that. Right? What are we gonna do? So as far as the logic, if we just map it out.\u003C/p>\n\u003Cp>Right? New chirp. We're gonna get the user from the chirp. User from the chirp. We're going to find all the users who are following that user push chirp to their feed.\u003C/p>\n\u003Cp>Alright, seems relatively straightforward. Whenever this chirp occurs, right now that we've got this flow set up, let's just go and test new flow. Hit save. Go back to our Flows, and now populate fees. We should have a payload.\u003C/p>\n\u003Cp>Right? We've got the content. We don't see the actual user, that is in our accountability object. Right? So we need to basically get a list of all the followers and their feeds.\u003C/p>\n\u003Cp>So how are we gonna do that? We are going to get a list of feeds. So we're gonna read data. Let's do like get feeds here. We'll do from the we'll do full access.\u003C/p>\n\u003Cp>We want to basically get a list of feeds. And the feeds here, we're gonna do a filter where the, we want to dig into this, right? So if we look at our other items, if we look at our data model, right. If we're getting a feed, there are there's a user for that feed. And then we want to inside the user, we have the followees.\u003C/p>\n\u003Cp>Actually, we wanna get all the followers and then their associated feeds as well. Okay. So let's go through that route instead. We're gonna get a list of all the followers where the follow e id is equal to the dollar sign accountability dot user. And for that we're also going to get a list of fields and we want to dive in and actually go through the nesting here.\u003C/p>\n\u003Cp>So Directus allows me to basically populate all the relational data in a single API call. So we'll do the root level fields, and then again if we're going through the followers we've got a follower ID. So we'll do follower ID, that'll get us to the user, dot feeds. Is that what we want? Follower ID dot feeds.\u003C/p>\n\u003Cp>And that should give us a list of the feeds that we want to populate. Alright, let's test this out and see. Alright. So if I chirp, the expected action here for this flow it should give me like John Doe's feed ID, if I've got that set up right. Right, because John Doe has feeds here.\u003C/p>\n\u003Cp>Alright, let's test it out and see. So we'll do show up in John John's feed. Hit save. And let's go test this flow. Alright.\u003C/p>\n\u003Cp>Alright. Less than a minute ago. Let's populate. Alright. So here's all of our feeds that we've got.\u003C/p>\n\u003Cp>We can see there's the feed, there's the or those are the followers actually. And then we need to collate all these feeds. So I'm just gonna copy this over here into Versus Code. And the next thing we're gonna do, we want to push that chirp into those feeds. So we're going to update a list of feeds and push the chirp into those feeds.\u003C/p>\n\u003Cp>Alright, so how we're gonna do that? We will go in and so we are going to update the feeds. And actually we probably need a like an intermediate here. So we're gonna return let's format the feeds. Alright so if we look we are getting well we got git feeds.\u003C/p>\n\u003Cp>So that's gonna be accessible through our data object as git feeds, Alright. So here's all the feeds, that is data.gitfeeds. And in that feed where you're wanting to map through those and we're going to return an array of the feed IDs that we want to push this tweet into. Alright so feeds to update equals feeds dot map. That's gonna be our feed.\u003C/p>\n\u003Cp>And then we're going to return what the feed dot follower_id feeds the first item in that array. Feeds to update, and then we're just gonna return feeds to update. Oops, gotta spell that correct. Alright, so just a little bit of formatting logic to make this easier. And then now what we're gonna do, let's call this get feeds ID.\u003C/p>\n\u003Cp>Get feeds ID. And that should give us an array and now we're gonna update those individual feeds. So we'll do update feed, update feeds. The collection is gonna be feeds from full access. And then as far as the IDs here I'm just gonna pass this array.\u003C/p>\n\u003Cp>So get feeds, Should've put IDs at the end of that, but no worries. And then we are going to do what? Can we do the can we do the array syntax here? Create chirpid. I think we can do this.\u003C/p>\n\u003Cp>So this is gonna be in, we need chirps. So that's the field that we're gonna update within the feed. And then we're gonna use the create update syntax here. So we'll do something like this, create. So instead of like modifying the value of these chirps, alright, we're gonna create a new record inside that array, which is gonna be what?\u003C/p>\n\u003Cp>What is that gonna be exactly? So we'll go back. Chirps are in our feed. Chirps, a chirp is what, just pass the chirp ID. Alright.\u003C/p>\n\u003Cp>And the chirpid is coming from what, trigger.key. Create trigger.chirp_, actually trigger.key. Alright. Is this actually gonna do what we wanna do or not? We'll see.\u003C/p>\n\u003Cp>Alright, let's test this out, right? So now if we take a look at our feed, let's do this side by side. Alright. So we'll go back over here. How are we doing on time?\u003C/p>\n\u003Cp>I don't know if we're gonna get to the like any front end stuff on this or not. Alright. So we got our feeds. Nothing in John Doe. Now if I chirp, please show up in feed.\u003C/p>\n\u003Cp>Refresh. It's not so there's something wrong with our logic here. Let's take a look at what's going on with this. So we got our feeds, run script, syntax missing. Forgot some type of feeds.\u003C/p>\n\u003Cp>Oh, of course forgot a parenthesis, that'll always do it. Alright. So let's just test again. Test again, please. Alright.\u003C/p>\n\u003Cp>If I refresh the feed, do we have the feed? We don't see any chirps in that feed over here. So something else is going wrong. There's our invalid payload. It must be of type 1 object.\u003C/p>\n\u003Cp>Would it just be passing the ID? ID. Let's add this in. Test again. Test one more time.\u003C/p>\n\u003Cp>Save. Refresh this. Okay, okay. Did we test one more time? If I look at the feed, we've got a feed for John Doe.\u003C/p>\n\u003Cp>It's not exactly what I wanted. It's pushing a new, I guess that would be like update instead of create. Update. Let's look at the directus docs actually. Create update.\u003C/p>\n\u003Cp>That's gonna be adding items, I think. Creating multiple items. Relationships. Where is this gonna be? Global parameters.\u003C/p>\n\u003Cp>Create an item. Relational data. Okay. So assign the existing item to be a child of the current item. You can use the same structure to select what the related items are.\u003C/p>\n\u003Cp>Simply omit them from the array. So we just wanna add an item in the array. You can provide an object detailing the changes. Like we don't wanna create a blank chirp. Right?\u003C/p>\n\u003Cp>Is this what happened here? No? Why is this chirp not showing any content? So it does show up in John Doe's feed. It doesn't show the actual content, which is weird.\u003C/p>\n\u003Cp>Let's just test one more time. So what is actually happening here? This is the chirp. Oh, duh. That's what's going on.\u003C/p>\n\u003Cp>Chirp underscore ID. So we're gonna create, and it should be chirp underscore ID because we've got to go through the junction collection. Alright. So we're gonna create a new item. And this should is that gonna solve this worse?\u003C/p>\n\u003Cp>Let's see. Alright. Chirp. We'll just test this. Feeds.\u003C/p>\n\u003Cp>We hit save. Now we see there's the chirp. Baller. Okay. So now that is working as expected, right?\u003C/p>\n\u003Cp>So if I now chirp, I'm the admin user and John Doe is following me. Follow me, John. I hit save. That shirt should show up in John's actual feed. Amazing, right?\u003C/p>\n\u003Cp>It doesn't show up in anybody else's feed. Awesome. Okay. So now that logic is working as intended. So we have all that working.\u003C/p>\n\u003Cp>Create a feed of chirps. We've gotta follow other users. We've got all this functionality. Right? Let's try to put together something on the front end for this.\u003C/p>\n\u003Cp>So what is this gonna look like? I'm using Tailwind and I've got the Nuxt UI library inside this thing. Tailwind Twitter clone maybe. Search for that. Tailwind Twitter clone pages, Tailwind clone with CSS.\u003C/p>\n\u003Cp>Hey, there we go. This is probably close enough. Right? It kinda looks a mess, but let's go for it. We're just gonna copy this code.\u003C/p>\n\u003Cp>Let's create a new page. Let's just call it feed dot view. Do some script setup. Oh, that didn't give me what we want. Script setup lang equals ts.\u003C/p>\n\u003Cp>And alright so now we're gonna add this thing here. This is a giant mess of HTML. Let's just load up our user over here. Let's get logged in. Are we logged in right now?\u003C/p>\n\u003Cp>We're missing a tag somewhere. Div div. Div. Where is this thing? Oh, see it's not even a particularly great clone because there's some missing div somewhere.\u003C/p>\n\u003Cp>Great. Lots of fun. Don't ever trust these templates that you see online. Let's still where's the issue? Is it that one little div there?\u003C/p>\n\u003Cp>Where is the issue? Div. Div. Div. Div.\u003C/p>\n\u003Cp>You know what? Forget it. 1st tweet start. I don't even tired of messing with this. Alright.\u003C/p>\n\u003Cp>So let's just go back to our index page. Wasting too much time to actually get anything done here. Alright. So we're gonna have a, what, list of tweets, chirps equals, we're gonna use the await. Use async data.\u003C/p>\n\u003Cp>We're gonna give it a key. This is the user chirps, user feed. Alright. And then we're gonna do return. We're gonna import our we're actually gonna grab that from our used Nuxt composable.\u003C/p>\n\u003Cp>So used Nuxt app. Okay. We'll await the user feed. And then we're gonna import the read items from the directus SDK atdirectus SDK, and we're gonna return directus dot request read items. Wait.\u003C/p>\n\u003Cp>Use async data. Return direct us read items. Chirps. Actually, we're gonna get the feed. Where the actually, let's get all the chirps.\u003C/p>\n\u003Cp>Chirps where the feed So if this is my user, and I've got the chirps, We wanna get the feeds, actually. Get the feed for my user where the, do I have the do I have a composable in here for the user? Where is my actual user state in this application? Let's take a look at our actual state. We have a user.\u003C/p>\n\u003Cp>We have a user ID. Okay. So we're also gonna do the user equals use state user. Okay. And then we're gonna get all the chirps where the user oh, we're gonna get the feed where the user is the current user.\u003C/p>\n\u003Cp>And actually, like a a lot of this could be controlled via access control here inside Directus. So I could just see I could see anybody's chirps. I could create chirps. I can't delete any chirps. But as far as the feed, I can only see my feed.\u003C/p>\n\u003Cp>So let's just adjust this. Alright. The permissions so I can only see my feed. So the user dot ID is equal to current user dot id. Alright.\u003C/p>\n\u003Cp>So now if I just do this, honestly. Feeds, chirps. Alright. We'll refresh, failed to resolve, directus sdk@directus/sdk. Alright.\u003C/p>\n\u003Cp>So and then maybe we'll just log those out. Pre chirps. Do we see any actual chirps data? Oh, actually gonna return that. We need to deconstruct that.\u003C/p>\n\u003Cp>Chirps. Okay. So there's our feed. And we actually want to see all the chirps that are in that specific feed. Right?\u003C/p>\n\u003Cp>So now if we log in, I'm not exactly sure who I'm logged in as. And let's update our user role inside Directus to where we can at least read the item permissions. We wanna be able to read our own user. So ID equals current user. And what's that gonna give us?\u003C/p>\n\u003Cp>Refresh. Alright. So I'm not logged in. If I log in as John Doe here I don't even know what password that I gave John. Pretty sure I do know.\u003C/p>\n\u003Cp>Example. Okay. So we're gonna go back. If we refresh, do we what do we see? Not seeing anything, actually.\u003C/p>\n\u003Cp>Default invalid user credentials. If we take a look at Wait. Use async data. Data feeds. It's gonna be the feed.\u003C/p>\n\u003Cp>Let's just move this logic somewhere else. Killing me. K. Index. Just go somewhere else with this.\u003C/p>\n\u003Cp>NuxLink. Or, view button to feed to the feed Batman. Alright. So there's our feed. Why don't I see any actual chirps inside there?\u003C/p>\n\u003Cp>To the feed, Batman. And we're also not getting this stuff on the actual server side as well. So that's another thing to figure out. But our access control settings here are preventing us from seeing the actual feeds inside the the chirps inside the feed. We probably wanna be able to see followers or what as well.\u003C/p>\n\u003Cp>If we go to the feed, now, can we actually see any of this project to the feed? Just totally blow this away. Got 9 minutes left on this episode. Really struggling here to kinda sort this one out. So this is our user's feed.\u003C/p>\n\u003Cp>We should be able to see the actual chirps within that though, and I'm not sure why that is not showing up. So there's the feeds, direct us users. You know, if I were to just do something like this, local host 8055/feeds, should be like actually items slash feeds. So there I can see the actual chirps that are coming in. But is that because we got, like, caching going on here?\u003C/p>\n\u003Cp>Cash. Is there, like, a cache? User ID. Okay. So now we can see the chirps.\u003C/p>\n\u003Cp>Okay. Let's actually show the chirps themselves fields. ID, we probably want the user information, and then we'll get the chirps. Chirps. And let's just get all the the details of the chirps.\u003C/p>\n\u003Cp>Okay. So those are the the that's the junction collection. We really want the chirp dotchirp_id dash star. Okay. So now we've got a list of the chirps.\u003C/p>\n\u003Cp>Let's just iterate over that. We'll do a, dev4. What, chirpinfeed.chirps. And we can actually, like, maybe deconstruct this a little bit. Chirp underscore ID as the chirp.\u003C/p>\n\u003Cp>Key is gonna be chirp dot ID. Thank you. Then we have the chirp content. We also probably want the user ID from the chirps. We wanna know that that specific user.\u003C/p>\n\u003Cp>And in that case I'm probably gonna need to adjust my permissions as well. So access control, we'll go into users. For here, we'll just allow all access, but let's just restrict certain fields. Alright? Can't see anything except for the ones that I want.\u003C/p>\n\u003Cp>First name, last name, avatar, we don't wanna show email. We'll show ID. Followers, followee. Okay. Alright.\u003C/p>\n\u003Cp>Chirps dot underscore id.user. What are we missing? Oh, chirpsfeed.chirps. Let's just back up a minute. Feed.\u003C/p>\n\u003Cp>Pre let's throw this up here. And I'm just gonna, like, show this. Let's refresh the page, see what we got going on here To the feed. Alright. Chirps, chirp ID as the chirp in feed dot chirps.\u003C/p>\n\u003Cp>Dot content. This should work. Not sure why it's not. Right. V if feed and feed dot chirps.\u003C/p>\n\u003Cp>Feed. Oh, duh. We gotta get the first item of the feed. Transform data data dot 0. Okay.\u003C/p>\n\u003Cp>Definitely a frustrating frustrating run here. Transform data. That should give us what we need. My gosh. Return, comma.\u003C/p>\n\u003Cp>Still not. Oh, this is okay if I put this in the right spot, what a mess. Okay. I think that that should have it now, and we got a grand total of, like, 3 minutes left anyway. Do we have a feed?\u003C/p>\n\u003Cp>Yeah. Okay. So there's the chirps. Man, when we fail, we spell spectacularly sometimes. Alright.\u003C/p>\n\u003Cp>So here we go. We've got our chirps. We're showing a feed of those chirps. We've got the user underscore ID. So we refresh that.\u003C/p>\n\u003Cp>Now we could see the actual user that is chirping about that. So and we'll have a divv4. Let's do like a flex, flex call, give these some gap. We'll give each tweet some padding. We'll show a div.\u003C/p>\n\u003Cp>We'll do the username. Username chirp dot user ID or underscore user ID. Admin user. There's all of my my actual feed. Great.\u003C/p>\n\u003Cp>And, this is really really lovely. Now what if we wanted to actually chirp a bit? Right? We would create a new function, async function. Doesn't even actually make sense.\u003C/p>\n\u003Cp>We got a minute 30 left. I'm gonna go ahead and call this one a fail for today. We're gonna call this. We've got the direct us in set up really nicely on the the front end. We just really struggled a bit.\u003C/p>\n\u003Cp>You know, just an off day for me. Happens every once in a while, right? So we've got our chirps over here. The feed is is working as intended, right? If I go in and if I were to set up another follower, like if Elon Busk follows me, admin user.\u003C/p>\n\u003Cp>Save that. Obviously, like he needs a feed as well. Elon Busk. Great. And then if I chirp again, chirp for Idan.\u003C/p>\n\u003Cp>In his feed, we can see that one shows up. So there's our chirp. We can see that showing up there, which is nice. Chirp to eat on. That chirp also showed up inside John Doe's feed.\u003C/p>\n\u003Cp>So that part of it, we nailed UI. Do the explosion here. 10 seconds left. Yeah, I I think this is a good exercise from a data modeling standpoint and kind of a how to put this together. Just wasn't all there on the front end today.\u003C/p>\n\u003Cp>That's the way some of these things go. That's it for this episode of 100 apps, 100 hours. Hope you'll join me for the next one. We'll see you.\u003C/p>","Alright. Alright. Alright. Welcome back to yet another episode of 100 Apps, 100 Hours. I'm your host Brian Gillespie, developer advocate here at Directus. And I got a doozy today. It's one, honestly, I've not been looking forward to. So if you're new to the show, basically, we rebuild or try to build some of your favorite app ideas in 60 minutes or less, or publicly fail trying. I fail a lot. But that's okay. We learn something each time. There are only two rules to this. You have 60 minutes to plan and build, which always ends up not being enough time. And rule number 2 is use whatever you have at your disposal, kind of the anti rule. So with that, let's fire into this episode, the social media platform. Again, I like I said, I've not been looking forward to this. What are we gonna be building? We're gonna try to build something similar to Twitter. Where we've got a feed, post from different people that we're gonna follow on social. And, that is what it is. So, again, it should be an interesting episode. Let's get started on the clock. Start the timer and away we go. Right? So when I look at social media platform like Twitter, you've got a list of posts, you've got users that are on the service, You can follow people. You can favorite their posts. But as far as the functionality we're really looking for out of this, let's define it right when we go to plan here. We're going to, register users, which I've already got set up, inside my example project that I use all the time in these shows. We want to follow other users, create a feed of chirps. We won't call them tweet. We'll call them chirps. Sounds great. Let's shrink this down. This is gonna be our functionality needed. Alright. That seems like a pretty good functionality set for this. Again, yeah, I can't stress how much fun I'm looking forward to having on this episode. Alright. Now as far as our data model, you know, when you kind of break it down, there's the obvious ones like users. Right? So if I do some diagramming here, we have our users. These are actually gonna come from our directus users collection that directus gives us out of the box. That gives us auth and permissions and all the fancy stuff that we're really interested in. So we'll have Directus users and then we've got the chirps. As far as the chirp, it probably has, what, like some content. Maybe there's an image or a video. You know, this could just be a file if we wanted to share that. And then a timestamp or, created at date. Right? Those are our chirps. Those have a relationship back to the user. There's a user that chirped, so we can represent that. And then, what else are we gonna have here? We're gonna have some followers. Alright. So, basically the the users are gonna follow themselves, but because that's a many to many relationship, we're gonna need like a separate table for this. So when we talk about this, we're gonna have a follower follower ID. So this is I am the follower. The followee. I always hate naming stuff. Followee ID, followedid. So again those are gonna be a two way relationship with users. And then, what else do we have? Right? The other part that I'm not a 100% sure about, we've got this feed that is unique for each user. Right? So to me that means a separate collection. So we have feeds, we've got a user that's associated to the feed. And then what else do we have? Right? We're gonna have followers, feeds. The feed is gonna be a or like updated at. When was the feed last updated? And trying to think of, like, the other paradigm here. We're gonna just have a, like, the actual tweets that are or chirps that are in that feed. Alright. And then that means we need, like, a junction table as well, feed chirps. So this will be the chirp and the feed. Alright. So I I think this is the the data model that I wanna go with for this. We'll see how this actually plays out. As far as what I've got set up, right, is the standard config that I use for all these episodes, if you've ever caught one of the past episodes. I've just got a little Nuxt starter application that has a a couple of routes, like a register and a login. Let me just clear my cookies so we can see what those look like. Right? Just a register and login route. I've got a Directus SDK plugin configured here. So we just create a REST client. There's also a real time client in case I decide that we need to use real time in this. And then on the other side of it, I've got a blank direct assistance, just a single admin user. So, alright. Let's get to work on our data model. That's what we're gonna bang out first. And like I said, we've already got direct us users here. Let's work on our chirps. So that will be the first collection that we create, and I could zoom way in inside the Directus instance here. And do I really am I concerned with the primary key field for these being UUID or auto incremented integer? You know, typically, I'm choosing UID, but in in this case, you know, maybe integer is is fine really. For created on, we're gonna do created at. And created by, we're just gonna do the user I'm gonna call that user ID. So whatever user creates this chirp, that'll be saved in the database. And then whenever they create this chirp, will be saved as well. Once the chirp is out there, it is gone. We are not going to allow editing or anything like that. And as far as our content, maybe we want to support markdown for this. Message content. Chirp content. Sounds good. And then, you know, let's get wild with it and maybe even support a file. You can upload a single file to your Chirp to share. Alright, so that's our model. I'm gonna unhide these other fields here just so I could see these when I look at them. And next I want to I'm just gonna chirp. Chirp chirp. Save it. And you'll see that saves the admin user and the date, and now we've got our first chirp for this social media platform. I don't know what we're gonna call this. Why? Why not? I think why is the example that, our CTO, Reich Van Zanten, used in one of his talks. So, alright. What's next? We're gonna do our followers. Right? So we have our followers. And in this case, we have got, really not concerned with any of those other fields. What we're gonna do, I want to go into my data model. I'm I'm gonna go to our system collection, right? And we're gonna do a Directus users, and we're gonna go in and create a many to many relationship here. It's gonna be followers. We're gonna go to the followers table. And what do we have here? We've got the directus user. This is the follower followee ID. This will be the follower ID, I think. Alright. Is that gonna work out? We got followers. We're good. And then we could add, oh, that's our where do we have that? Follow words collection. Yeah. We don't need to add that. Right? Great. And then if we delete any of these other items, we're basically just going to like if somebody if a user gets deleted or a follower gets deleted, we're just going to remove those. So okay. I hope that's gonna give us what we're looking for. So now we've got a followers table and junction direct us users followers. Oh, okay. So it's created that extra table for me. Why did it do that? Because I have a relationship there? That's some unexpected behavior, but that'd be okay. We'll roll with that. So on our users, this table is pretty much worthless, we'll just remove that. Unexpected error. Okay, fun stuff as always. Let's let's try this over again, right? We've got followers, we're just gonna delete that. Now that table is removed. We'll do another mini to mini. And we'll call it followers. And actually the related collection here we want is direct us users and not followers. So that's where I goofed up originally. And we're gonna call this followers. That'll be our new collection. We got the followee ID and then the follower ID. And I think this will get us what we want. So this will be followees. Alright. Let's try that. See if that gets us what we want. So now we have followers and followees. And then we have a table called followers that we can inspect and make sure everything is looking correct. Alright, great. So we've got that set. Now we're gonna work on our feeds, right? Whenever I want to, I like check my feed, I I need some way to store the posts that are going into that specific feed. So we'll have a feeds collection. Again this could be auto incremented. And for the feed side of it, basically we're gonna have a when was this feed updated? Let's do updated at. Just carry that same one. I I really love these optional system fields inside Directus. Again, it just gives you some of that functionality that you need like time stamps and status and things like that baked right out of the gate. Alright. So we've got a feed. We've got updated at. We've got a we're gonna have to add a user to this feed. Right? So this is a user that's gonna be a mini to 1 relationship to the user's collection, direct to users. And then if I open up the advanced settings for for any of these fields that I'm creating, I could go in and create the corresponding field. Right? So I can add additional feeds for this specific user. You know, maybe we wanted to subscribe to different topics or something like that. So on delete of directus users, we wanna delete the feeds item because there's no need for a feed for a user that is not around, right? And when it comes to our display template, I can also go in and control what this will actually display here as well. Just a nice thing if you're doing a lot of editing inside the Directus Data Studio. So we got the updated at. There's the user. And then we're gonna need another relationship between the chirps and the feed, right? So we need to know what are all the chirps within that specific feed. So, how are we gonna do this? That'll be a many to many relationship again. Because a one chirp could be in many different feeds. So we'll go here inside Directus, we'll just search for the many to many relationship, right? And we are currently where? We're on feeds. So this will be our chirps. We'll add chirps as the related collection here, we'll show a link. And I'm gonna go into the relationship under advanced settings in case I want to add this, right? This is the feed ID, this is the chirp ID. Sounds great. Looks good. And then we can add the inverse relationship that we want. So we could look at a chirp and see all the feeds that it's involved in. Not sure we necessarily need that either. As far as the sort field, we're just gonna sort by the the timing of the specific chirp. Alright. So I with that, I think we've got our data model, like, pretty fleshed out here. We got the users already. We got chirps. We got followers. We got feeds. Cool. As a user, I could go in and I could follow someone, followee, except I don't have any actual users to follow at this point, right? So if we turn our attention to the front end of the project, I can go ahead and register a new user, Right. Let's call this edonbusk@example.com. And we'll give this a very secure exam model. Okay. Alright. So if we go to log in now, we should see we're already logged in. Great. Do I see a user here? I do. Elon Busk. Should've added a first name and last name to the actual registration form. Cool. So now I've got users that I can actually follow inside this. So if I go into my admin user, I should be able to add Elon Busk as one of my followees. Man, that terminology is gonna trip me up the whole time. I should have done something else. Following or something like that. Alright, so now, we don't have a feed for this. So one of the things that we could do inside Directus, whenever a a new user is created, you probably want to add a feed for them, right? So a couple ways you could go about that. One way is just flows, right? So flows are automations inside Directus that we can create. And I could trigger these based on a number of different actions. So we're just gonna do a create profile or create feed for new users. Okay. As far as a trigger setup we're going to do an event hook. And the action we want to be is non blocking. And should be whenever we is there a way to do it? Whenever a user is created auth.create. Auth.update. So whenever items are created and I just am going to do the Directus users collection here. So we're gonna trigger this whenever a new user is created, right? If I go in and I'll just throw these up side by side. Right? I'm already logged in. Let me just destroy this. Now we'll register a new user, new user at example.com. New password. User has been created. If I refresh this we could see that. Great. We can see there's the user, there's the key that we're interested in. Alright. And now I'm just gonna go in and create a feed for this, create feed. Alright, so there's our feed. And as far as the feed data model, I'm just gonna duplicate this because I do not remember what that looked like. Hazard of the gig. Right? So if I look at the feed, we've got a user and we've got chirps. So we'll just have one param that we're gonna pass. And we could even add like a a system message if we wanted to. But for now let's just create the feed. So we'll add an operation for it, we're gonna create data, we're gonna create a feed. And then inside the payload we've got the user key. And then we're gonna dynamically pull information from the trigger for this. So trigger dot key. And we'll just use that mustache syntax. And this should get us what we want. We'll do full access. Let me just check that one more time. Trigger dot key. Okay, great. Save. And now let's try this again. Right? I see 0 feeds in here. I'm just going to remove my session token. And we'll try john@example.com. Password. So we should have a John and example here. And we can see we've got a feed for that user now as well. Great. Perfect. Alright, so if we go back to our functionality that we need, we can register users, right. Do we have the ability to follow other users? In this case, yes, we do. We don't have it wired up to the front end yet. But let's let's stay focused on Directus, right? If I am following someone, and this is not showing up really nicely. So let's just edit the interface for this. We are going to show the avatar thumbnail with a first name, last name. And I can just copy this display template here and I can do that same thing here. So the the difference between the interface and the display, the interface shows up on the form itself. So when you're in that detail view, the display shows up on the layouts. So if I'm here looking at a list of followers, this is the display and this is the actual interface, right. So let's go back into our followers and we'll we'll fix this one as well. Should just be able to copy paste the same thing. Great. Cool. Cool. There it is. We got our followers, followees. Now if I am following Elon Musk, great name by the way, anytime he chirps it should populate into my feed, right? So, if I look at my feed, I don't have a feed yet, So let's create a feed for me. And here's, here's my feed. I can't really tell which one that is, so we'll just fix that really quickly as well. We'll show a display the direct us user. Great. Cool. Alright. So now I can see there's my feed, there's John's feed. Anytime Elon Dusk tweets, I want to populate that into, a feed. Right? Or let's say if I go into John's user, for example, John Doe, maybe John Doe is following me. So if we add me as an admin user, that'll be a good example. Right? If I chirp, chirp again. Right? This is, again, this is just writing it to the SQL database. Direct is is a is a nice complement to that. Like whatever changes I'm making here being mirrored. But, I chirp. If I go to the feed, nothing happens for John Doe. Right? So whenever a new chirp comes in, I keep wanting to call it a tweet, I want to basically populate that feed for all the other users that are, following that user, right? So again, I could reach for flows on this, populate feeds. Now at the scale of something like x or or Twitter or Facebook, this is going to probably break really quickly. Because, you know, you could have millions of followers and, you're gonna want to look at other solutions to scale this. But again, this is what we can build in an hour, so let's do that. So whenever an item is created, a chirp, we're gonna do some logic on that. Right? What are we gonna do? So as far as the logic, if we just map it out. Right? New chirp. We're gonna get the user from the chirp. User from the chirp. We're going to find all the users who are following that user push chirp to their feed. Alright, seems relatively straightforward. Whenever this chirp occurs, right now that we've got this flow set up, let's just go and test new flow. Hit save. Go back to our Flows, and now populate fees. We should have a payload. Right? We've got the content. We don't see the actual user, that is in our accountability object. Right? So we need to basically get a list of all the followers and their feeds. So how are we gonna do that? We are going to get a list of feeds. So we're gonna read data. Let's do like get feeds here. We'll do from the we'll do full access. We want to basically get a list of feeds. And the feeds here, we're gonna do a filter where the, we want to dig into this, right? So if we look at our other items, if we look at our data model, right. If we're getting a feed, there are there's a user for that feed. And then we want to inside the user, we have the followees. Actually, we wanna get all the followers and then their associated feeds as well. Okay. So let's go through that route instead. We're gonna get a list of all the followers where the follow e id is equal to the dollar sign accountability dot user. And for that we're also going to get a list of fields and we want to dive in and actually go through the nesting here. So Directus allows me to basically populate all the relational data in a single API call. So we'll do the root level fields, and then again if we're going through the followers we've got a follower ID. So we'll do follower ID, that'll get us to the user, dot feeds. Is that what we want? Follower ID dot feeds. And that should give us a list of the feeds that we want to populate. Alright, let's test this out and see. Alright. So if I chirp, the expected action here for this flow it should give me like John Doe's feed ID, if I've got that set up right. Right, because John Doe has feeds here. Alright, let's test it out and see. So we'll do show up in John John's feed. Hit save. And let's go test this flow. Alright. Alright. Less than a minute ago. Let's populate. Alright. So here's all of our feeds that we've got. We can see there's the feed, there's the or those are the followers actually. And then we need to collate all these feeds. So I'm just gonna copy this over here into Versus Code. And the next thing we're gonna do, we want to push that chirp into those feeds. So we're going to update a list of feeds and push the chirp into those feeds. Alright, so how we're gonna do that? We will go in and so we are going to update the feeds. And actually we probably need a like an intermediate here. So we're gonna return let's format the feeds. Alright so if we look we are getting well we got git feeds. So that's gonna be accessible through our data object as git feeds, Alright. So here's all the feeds, that is data.gitfeeds. And in that feed where you're wanting to map through those and we're going to return an array of the feed IDs that we want to push this tweet into. Alright so feeds to update equals feeds dot map. That's gonna be our feed. And then we're going to return what the feed dot follower_id feeds the first item in that array. Feeds to update, and then we're just gonna return feeds to update. Oops, gotta spell that correct. Alright, so just a little bit of formatting logic to make this easier. And then now what we're gonna do, let's call this get feeds ID. Get feeds ID. And that should give us an array and now we're gonna update those individual feeds. So we'll do update feed, update feeds. The collection is gonna be feeds from full access. And then as far as the IDs here I'm just gonna pass this array. So get feeds, Should've put IDs at the end of that, but no worries. And then we are going to do what? Can we do the can we do the array syntax here? Create chirpid. I think we can do this. So this is gonna be in, we need chirps. So that's the field that we're gonna update within the feed. And then we're gonna use the create update syntax here. So we'll do something like this, create. So instead of like modifying the value of these chirps, alright, we're gonna create a new record inside that array, which is gonna be what? What is that gonna be exactly? So we'll go back. Chirps are in our feed. Chirps, a chirp is what, just pass the chirp ID. Alright. And the chirpid is coming from what, trigger.key. Create trigger.chirp_, actually trigger.key. Alright. Is this actually gonna do what we wanna do or not? We'll see. Alright, let's test this out, right? So now if we take a look at our feed, let's do this side by side. Alright. So we'll go back over here. How are we doing on time? I don't know if we're gonna get to the like any front end stuff on this or not. Alright. So we got our feeds. Nothing in John Doe. Now if I chirp, please show up in feed. Refresh. It's not so there's something wrong with our logic here. Let's take a look at what's going on with this. So we got our feeds, run script, syntax missing. Forgot some type of feeds. Oh, of course forgot a parenthesis, that'll always do it. Alright. So let's just test again. Test again, please. Alright. If I refresh the feed, do we have the feed? We don't see any chirps in that feed over here. So something else is going wrong. There's our invalid payload. It must be of type 1 object. Would it just be passing the ID? ID. Let's add this in. Test again. Test one more time. Save. Refresh this. Okay, okay. Did we test one more time? If I look at the feed, we've got a feed for John Doe. It's not exactly what I wanted. It's pushing a new, I guess that would be like update instead of create. Update. Let's look at the directus docs actually. Create update. That's gonna be adding items, I think. Creating multiple items. Relationships. Where is this gonna be? Global parameters. Create an item. Relational data. Okay. So assign the existing item to be a child of the current item. You can use the same structure to select what the related items are. Simply omit them from the array. So we just wanna add an item in the array. You can provide an object detailing the changes. Like we don't wanna create a blank chirp. Right? Is this what happened here? No? Why is this chirp not showing any content? So it does show up in John Doe's feed. It doesn't show the actual content, which is weird. Let's just test one more time. So what is actually happening here? This is the chirp. Oh, duh. That's what's going on. Chirp underscore ID. So we're gonna create, and it should be chirp underscore ID because we've got to go through the junction collection. Alright. So we're gonna create a new item. And this should is that gonna solve this worse? Let's see. Alright. Chirp. We'll just test this. Feeds. We hit save. Now we see there's the chirp. Baller. Okay. So now that is working as expected, right? So if I now chirp, I'm the admin user and John Doe is following me. Follow me, John. I hit save. That shirt should show up in John's actual feed. Amazing, right? It doesn't show up in anybody else's feed. Awesome. Okay. So now that logic is working as intended. So we have all that working. Create a feed of chirps. We've gotta follow other users. We've got all this functionality. Right? Let's try to put together something on the front end for this. So what is this gonna look like? I'm using Tailwind and I've got the Nuxt UI library inside this thing. Tailwind Twitter clone maybe. Search for that. Tailwind Twitter clone pages, Tailwind clone with CSS. Hey, there we go. This is probably close enough. Right? It kinda looks a mess, but let's go for it. We're just gonna copy this code. Let's create a new page. Let's just call it feed dot view. Do some script setup. Oh, that didn't give me what we want. Script setup lang equals ts. And alright so now we're gonna add this thing here. This is a giant mess of HTML. Let's just load up our user over here. Let's get logged in. Are we logged in right now? We're missing a tag somewhere. Div div. Div. Where is this thing? Oh, see it's not even a particularly great clone because there's some missing div somewhere. Great. Lots of fun. Don't ever trust these templates that you see online. Let's still where's the issue? Is it that one little div there? Where is the issue? Div. Div. Div. Div. You know what? Forget it. 1st tweet start. I don't even tired of messing with this. Alright. So let's just go back to our index page. Wasting too much time to actually get anything done here. Alright. So we're gonna have a, what, list of tweets, chirps equals, we're gonna use the await. Use async data. We're gonna give it a key. This is the user chirps, user feed. Alright. And then we're gonna do return. We're gonna import our we're actually gonna grab that from our used Nuxt composable. So used Nuxt app. Okay. We'll await the user feed. And then we're gonna import the read items from the directus SDK atdirectus SDK, and we're gonna return directus dot request read items. Wait. Use async data. Return direct us read items. Chirps. Actually, we're gonna get the feed. Where the actually, let's get all the chirps. Chirps where the feed So if this is my user, and I've got the chirps, We wanna get the feeds, actually. Get the feed for my user where the, do I have the do I have a composable in here for the user? Where is my actual user state in this application? Let's take a look at our actual state. We have a user. We have a user ID. Okay. So we're also gonna do the user equals use state user. Okay. And then we're gonna get all the chirps where the user oh, we're gonna get the feed where the user is the current user. And actually, like a a lot of this could be controlled via access control here inside Directus. So I could just see I could see anybody's chirps. I could create chirps. I can't delete any chirps. But as far as the feed, I can only see my feed. So let's just adjust this. Alright. The permissions so I can only see my feed. So the user dot ID is equal to current user dot id. Alright. So now if I just do this, honestly. Feeds, chirps. Alright. We'll refresh, failed to resolve, directus sdk@directus/sdk. Alright. So and then maybe we'll just log those out. Pre chirps. Do we see any actual chirps data? Oh, actually gonna return that. We need to deconstruct that. Chirps. Okay. So there's our feed. And we actually want to see all the chirps that are in that specific feed. Right? So now if we log in, I'm not exactly sure who I'm logged in as. And let's update our user role inside Directus to where we can at least read the item permissions. We wanna be able to read our own user. So ID equals current user. And what's that gonna give us? Refresh. Alright. So I'm not logged in. If I log in as John Doe here I don't even know what password that I gave John. Pretty sure I do know. Example. Okay. So we're gonna go back. If we refresh, do we what do we see? Not seeing anything, actually. Default invalid user credentials. If we take a look at Wait. Use async data. Data feeds. It's gonna be the feed. Let's just move this logic somewhere else. Killing me. K. Index. Just go somewhere else with this. NuxLink. Or, view button to feed to the feed Batman. Alright. So there's our feed. Why don't I see any actual chirps inside there? To the feed, Batman. And we're also not getting this stuff on the actual server side as well. So that's another thing to figure out. But our access control settings here are preventing us from seeing the actual feeds inside the the chirps inside the feed. We probably wanna be able to see followers or what as well. If we go to the feed, now, can we actually see any of this project to the feed? Just totally blow this away. Got 9 minutes left on this episode. Really struggling here to kinda sort this one out. So this is our user's feed. We should be able to see the actual chirps within that though, and I'm not sure why that is not showing up. So there's the feeds, direct us users. You know, if I were to just do something like this, local host 8055/feeds, should be like actually items slash feeds. So there I can see the actual chirps that are coming in. But is that because we got, like, caching going on here? Cash. Is there, like, a cache? User ID. Okay. So now we can see the chirps. Okay. Let's actually show the chirps themselves fields. ID, we probably want the user information, and then we'll get the chirps. Chirps. And let's just get all the the details of the chirps. Okay. So those are the the that's the junction collection. We really want the chirp dotchirp_id dash star. Okay. So now we've got a list of the chirps. Let's just iterate over that. We'll do a, dev4. What, chirpinfeed.chirps. And we can actually, like, maybe deconstruct this a little bit. Chirp underscore ID as the chirp. Key is gonna be chirp dot ID. Thank you. Then we have the chirp content. We also probably want the user ID from the chirps. We wanna know that that specific user. And in that case I'm probably gonna need to adjust my permissions as well. So access control, we'll go into users. For here, we'll just allow all access, but let's just restrict certain fields. Alright? Can't see anything except for the ones that I want. First name, last name, avatar, we don't wanna show email. We'll show ID. Followers, followee. Okay. Alright. Chirps dot underscore id.user. What are we missing? Oh, chirpsfeed.chirps. Let's just back up a minute. Feed. Pre let's throw this up here. And I'm just gonna, like, show this. Let's refresh the page, see what we got going on here To the feed. Alright. Chirps, chirp ID as the chirp in feed dot chirps. Dot content. This should work. Not sure why it's not. Right. V if feed and feed dot chirps. Feed. Oh, duh. We gotta get the first item of the feed. Transform data data dot 0. Okay. Definitely a frustrating frustrating run here. Transform data. That should give us what we need. My gosh. Return, comma. Still not. Oh, this is okay if I put this in the right spot, what a mess. Okay. I think that that should have it now, and we got a grand total of, like, 3 minutes left anyway. Do we have a feed? Yeah. Okay. So there's the chirps. Man, when we fail, we spell spectacularly sometimes. Alright. So here we go. We've got our chirps. We're showing a feed of those chirps. We've got the user underscore ID. So we refresh that. Now we could see the actual user that is chirping about that. So and we'll have a divv4. Let's do like a flex, flex call, give these some gap. We'll give each tweet some padding. We'll show a div. We'll do the username. Username chirp dot user ID or underscore user ID. Admin user. There's all of my my actual feed. Great. And, this is really really lovely. Now what if we wanted to actually chirp a bit? Right? We would create a new function, async function. Doesn't even actually make sense. We got a minute 30 left. I'm gonna go ahead and call this one a fail for today. We're gonna call this. We've got the direct us in set up really nicely on the the front end. We just really struggled a bit. You know, just an off day for me. Happens every once in a while, right? So we've got our chirps over here. The feed is is working as intended, right? If I go in and if I were to set up another follower, like if Elon Busk follows me, admin user. Save that. Obviously, like he needs a feed as well. Elon Busk. Great. And then if I chirp again, chirp for Idan. In his feed, we can see that one shows up. So there's our chirp. We can see that showing up there, which is nice. Chirp to eat on. That chirp also showed up inside John Doe's feed. So that part of it, we nailed UI. Do the explosion here. 10 seconds left. Yeah, I I think this is a good exercise from a data modeling standpoint and kind of a how to put this together. Just wasn't all there on the front end today. That's the way some of these things go. That's it for this episode of 100 apps, 100 hours. Hope you'll join me for the next one. We'll see you.","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":152,"year":153,"episodes":154,"show":164},"14fda5f2-95de-4dbe-a4e2-3fd956c21c19",2,"2024",[155,156,157,158,159,160,161,162,122,163],"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","d072a935-906e-4208-a5dc-e9b117d0ab29","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","383c24d5-b6b5-4d66-aba6-6997af5f77b4",{"title":165,"tile":166},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"title":8,"meta_description":8},{"id":169,"slug":170,"season":171,"vimeo_id":172,"description":173,"tile":174,"length":175,"resources":8,"people":8,"episode_number":176,"published":177,"title":178,"video_transcript_html":179,"video_transcript_text":180,"content":8,"seo":181,"status":133,"episode_people":182,"recommendations":184},"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",[183],"cc9262db-1ae2-4bc1-a1f9-7d2fc51a396d",[],{"reps":186},[187,243],{"name":188,"sdr":8,"link":189,"countries":190,"states":192},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[191],"United States",[193,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],"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":244,"link":245,"countries":246},"Michelle Riber","https://meetings.hubspot.com/mriber",[247,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,224,435,436],"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",1773850451623]