Module 1 - Introduction
1. Welcome to the course2. Why Go3. Why start and build a blog?4. What about React/Vue/Angular?5. Getting setup and source filesModule 2 - Tech Stack Walkthrough
1. Introduction to Golang Part 12. Introduction to Golang Part 23. Introduction to Golang Part 34. Structuring Golang Applications5. Templating with Templ6. Just enough interactivity with HTMX7. Getting started with postgres8. Servers, routers and endpointsModule 3 - Creating the MVP
1. What are the minimal requirements?2. Doing some initial plumbing3. Embedding static assets4. Creating our first views5. Tailwind & Utility-first CSS6. Styling the Landing Page7. Styling the Article PageModule 4 - Managing Content
1. Choose your own adventure2. Writing in Markdown3. Parsing Markdown to HTML4. Frontmatter and Meta Information5. Making our code examples look nice6. Adding error pagesModule 5 - Adding the Database
1. What is a Migration?2. Our first migration: articles table3. Creating the Database Layer4. Showing the Latest Posts5. Slugs and Human Readable URLsModule 6 - Managing the Blog
1. What are the minimum requirements?2. A new layout approaches3. Introduction to authentication4. Our second migration: Users Table5. Storing passwords securely6. Authenticating users Part One7. Authenticating users Part Two8. Remember me/Forget me9. Managing posts using a hypermedia API - Part One10. Managing posts using a hypermedia API - Part Two11. Managing posts using a hypermedia API - Part Three12. Managing posts using a hypermedia API - Part Four13. Implementing CRUD For Articles - Part One14. Implementing CRUD For Articles - Part Two15. Implementing CRUD For Articles - Part Three16. Implementing CRUD For Articles - Part Four17. Flashing Ourselves/Providing Visual Feedback - Part One18. Flashing Ourselves/Providing Visual Feedback - Part Two19. Flashing Ourselves/Providing Visual Feedback - Part ThreeModule 7 - Adding Subscribers
1. What are the minimum requirements?2. Expanding the database: Tokens & Subscribers3. Creating the token and subscriber models - Part One4. Creating the token and subscriber models - Part Two5. Creating the subscription form6. Saving and verifying subscribers - Part One7. Saving and verifying subscribers - Part Two8. Saving and verifying subscribers - Part Three9. Emails and Clients - Part One10. Emails and Clients - Part Two11. Emails and Clients - Part Three12. Our fifth migration: Tokens Table13. Email validation view14. Email validation tokens15. Sending validation emails with SES16. Making it all come togetherAdding Subscribers
Creating the token and subscriber models - Part One
Summary
Coming soon.
Transcript
Alright, so with the database stuff out of the way, we can begin implementing our models. So jump into models, create a new file called subscriber.go, package models. Here we're going to create a subscriber struct that will have an ID, a new UID, we have created at as a time.time, updated at, time.time again, we will have email, time, no that's a string, of course that's a string, and then we will have isVerified, that's a boolean. And now we're going to be transforming from the database representation to the model representation quite a few times here, so I'm going to create a helper function called convert, let's just call it convertDB to subscriber, that takes in the row from DB subscriber and returns us a subscriber in the way that we represent it in the application. So return, oh, just going to map the fields here, fill subscriber, so row.id, row.created at.time, row.updated at.time, row.email, and row.isVerified. So we have the standard libraries types, great. Now we're going to need two get functions, we need one for getting the subscriber by their ID and one for their email. So let's just quickly create the one for the ID, so getSubscriber, takes in the context, the DBTX as always, DBTX and ID, will either return a subscriber or an error. So in here we can say, let's just save this, say row, error, DB, STMTS, querySubscriberByID, pass the data, again we just return an empty subscriber if there is an error, and finally we can say return, convertDB to subscriber, or nil. Great, now for our getSubscriberByEmail, it's going to look really, really similar, we're just going to accept a string instead, queryByEmail, and then pass the email. Alright, next up I want to create the model functions to create and update a subscriber. So we're going to start by creating the newSubscriberPayload. Here we will say it needs an email, it also needs a, do we need subscribed, no, we're going to deal with that. So we actually just need the email, and we're going to be doing quite a bit more validation now, so I'm going to introduce a new library called Validate, that is very well known in the Go community, so to start doing validation here we add the struct tags, some people don't like it, I think it's perfectly fine to work with validation, so we literally just add this, and we can also add custom validation logic or custom validation rules if we wanted to, but for now we can just rely on the one that they shift with as a standard, because we just need the email to be required, so it's not null, and we need it to be an email, so again they will validate the email here. Then we can go up here and grab this, and change it to newSubscriber, that will take in the payload of newSubscriberPayload, and again return us a subscriber or an error. So the way we would validate now that the payload is in the correct state would be to say if error equals validate struct, and then we pass the payload, we say error is not equal nil, return subscriber, or have us join, say error domain validation, and then the error. So this just becomes a little bit more of a scalable approach to doing validation, when we start to validate lots of different fields. And you can see we also end up a little bit more cleaner syntax than we did when we last did the user validation, like by hand or manually, right, so this is a little bit more clean. Then let's just create a time.now and say params is db.insertSubscriberParams, and let's just have it fill out, so it's going to create a new UUID, we can actually just take these timestamps and use it up here instead, so time is going to be now, it's going to be time.now, and it will be valid, so we can just say now and now. Then payload, email, and at this point we don't know if the user email is verified, so we can say bool, false, and valid, true. You could also just leave it out, but I don't know, this is a bit more explicit. So then we're saying, zero, error, db.statements, and insert subscriber, context, and then the params. Again, we just return the empty subscriber struct, and the error if there is any. Finally, we just call the, as we did up here, we can just grab it, return the converted row to our subscriber struct. Now, for our update, it's going to be, again, very similar, we can steal a lot from our new subscriber method, and say instead of new, we're going to say update, and we're going to accept our ID, uuID, and we will also accept an isVerified, that's a boolean. Then we just add the validation logic again, required, and we actually, let's make this required, and this one here, instead of email, it's just going to be uuID, so we also get validated that the uuID is a valid shape. Then, update subscriber, and update subscriber payload, one more time, validate the struct, and we also use the now to add, so we can update the updated add fields, and we just need to say update subscriber params, we don't have to create it yet, we have the email, we have this verified that needs to come from the payload now, isVerified, finally, update subscriber, and convert it into our definition of a subscriber from the database struct. Now, our token models is going to look a little bit different, because we need it to be generic in a sense, so it's very nice when you can reuse this functionality, have different kinds of scopes, and use cases, and you can use it across different kinds of resources. So, we go into models again, we create a new file called token.go, again, of course, package models, and the first thing we need is something to create a hash, and we're going to be using, in the standard library, with this HMAC here, let me just get the import from the crypto library, and we can see here we just get a HMAC hash using the given hash key, the given hash and the key, so we want here to have SHA-256, new, and we want to have a key, so here we need to accept another ENV variable, so I'm going to call this token signing key, so that we get a hash that is based on this method type, and the key that we have provided, so it's unique to our application. Great, then we're going to have two types here, we're going to have scope, it's going to be string, and resource, that is also going to be a string. For our purposes now, we only need a scope for email verification, so let's create this now, email verification, that's a type scope, this is email.verification, later on we're going to add something for newsletter, and unsubscribing, and all the other stuff that we're eventually going to need, but for now we only need the verification. Then a little bit further down, we're going to create resource, subscriber, resource, and it's just going to map to our subscribers table, so that whenever we create these tokens, we're going to be passing the scope and the resource, and we can then, in the code, differentiate what resources it were to pull it, and all these kind of things. Finally, we need the meta information, that's going to have the resource, that's a resource, it's going to have the resource ID, so UUID, it's going to have the scope, that is a scope, and here I also want to introduce the validation, so validate, and here it's just going to be required, same for scope, it's also going to be required, and the resource ID is just going to be required, and needs to be a UUID. Finally, we can add the token type, so token struct, and we'll have ID, again UUID, created at time.time, you know the drill, at this time, at this point in the course, expiration, and hash, that's a string, and then finally the meta, the meta information, there we go, so again, we can copy all of this, say this one is going to be, and also required, and also required, finally, let's just create a little helper method here called isValid, so we can check if the token is expired or not, so we can say time now, before, and then we pass the expiration field, this way we can quickly, once we pull out the token, check if this is expired or not.