Database Migration Setup
A new users table is created via migration, including fields for ID, timestamps, email (unique and not null), and password (byte type). Both up and down migrations are defined to create and drop the table.
SQL Queries for User Operations
Queries are implemented to retrieve a user by email, insert a new user, and update an existing user. Each query returns the affected row and uses parameterized inputs for safety.
User Model Definition
A User struct is defined with ID, timestamps, email, and password fields. A helper function converts database rows into the user model.
Find User by Email Logic
A model-layer function retrieves a user by email, normalizing the email to lowercase before querying. The result is mapped into a User struct.
Password Validation Structure
A passwordPair struct is introduced to hold both password and confirmation password. Validation rules enforce required fields and length constraints.
Custom Password Match Validation
A custom validator ensures that password and confirmation password match. The validation is registered so it automatically runs when processing user input.
Create User Workflow
A CreateUser function validates input, constructs query parameters, and inserts a new user into the database. For now, the password is stored in plain text, with hashing deferred to the next step.
Transition to Authentication Handling
The episode concludes by preparing to implement password hashing and authentication logic in the next phase.
Right, let's do some plumbing. We need a users table. We need some curves to interact with the users table. And we need some models. Nothing in this episode will be new. We have seen all of this before. So I'm going to run through it rather fast so we can get to the new and interesting part where we deal with authentication and actually creating the form to log in and all of those elements. So let's just quickly create a new migration, create users table.
There we go, we have a new migration file. And in here, we're gonna say create table, if not exist, users. And we can simply grab from the articles table here. We're gonna need all of these. Close that. Then say email is vaja255.
not null and unique. Then we need the password and we can use a new data type here called by tier not null as well. So we guarantee that we have an email and we have a password. Then we say drop table if not exists users and we have our migration and we have our down migration as well. Right.
In Qwish, we can now create the, come on. Why can't I, there we go, users.sql. We're gonna need our query to, query user, query user by email. We expect to get one back. So select star from users where email equals,
The first argument, we need a query to insert, insert user. We expect to get one back again. So insert into users ID, created ads, updated ads, email, and finally pass word. The values will be,
argument one for the ID and then we just use now and now argument two for the email and argument three for the password returning star and our user insert user query is done finally for good measure let's just add an update user we again expect to get one back so update users set
updated at, updated at equals to now, email to argument two, and password to argument three, and then we use the where ID equals argument one, finally returning everything in the newly updated row. We jump out, just generate,
curious, and we are ready to create our user model. So in the models directory, I'm going to create a new file called user.go. Of course, in package models, we create our model struct that will have ID, UUID, create it at time.time, then update it at.
Then we have our emails string, and then we have our password, which is a byte slice. Nothing new here again. We will be needing the row to user function, like we have in article, we have row to article, associate row to user, row, DB user, we return, actually we just return a user right. We have no possible errors.
So we say return user. Let's give it a save to get the import. And then we can fill it out with code actions. So, row.id, row.createdat.time, row.updatedat.time, row.email, and then row.password.
and our row to user function is done, then we need to create the find user by email and the create user model function. So we say funk, find user by email. We have context.context, we pass the dbtx as always, and then the email that we want to look up. We return a user on error.
So row, error, queries, find our query user by email, passing the values. And then we say strings to lower. So we always operate on a lower email. Return an empty user struct. And then we simply say return row to user, pass the row.
and null since there is no error. Again, pretty straightforward. But for our create user function, we are going to be creating another custom validation type sort of because we need to accept our password and our confirmed password. So we guarantee that the password is what the user or ourselves in this case expect. So I'm going to create a new struct called password pair.
That's a struct. And we have password. That's a string and then confirm password. That is also a string. Then you'll say validate. Required. We say minimum has to be eight characters and a maximum to be 72 characters. And the same has to be true for the
confirm password. Then we can create our create user data struct where we have the email as a string. We can validate and say it's required and it has to be email and we can maximum have 255 characters. Then we have password pair which is a type of password pair. Now before we create the
Function to create the user, we need to do a custom validation method so that we check the value of confirmed password and password against each other before we try to create the user in the database. So I'm back in model starts go here and you'll create a function called func validate passwords match. We grab this SL
argument as a type of validator struct level. We say pvpair for password pair is equal to slcurrent interface password pair just as the published article validation. All right, good. So pvpair.password does not equal
pvpair.confirmpassword. And then we will say it's a report error. And in here we will say pvpair.pvpair. No, pvpair. There's a lot of pvpairs here. Password. And then password. Password.
It must match confirm password. Right. What am I doing wrong here? If this is the case, then we do SL report error. Let me get this and let me close it and then add this. Close this and do this. Now we need here.
KSL report error. Ah, we of course need to add an empty string here. Grab this and then we simply do the same for confirm password. So confirm, confirm and then must match password.
Right now we have our validated passwords match validation. We just need to register. So validates password match and we do it on the password pair. And then we have this happen for us automatically whenever we try to interact with this struct. So back into models.
user and now we can say funk create user pass what we always pass dbtx of dbtx and then data of create user data return also a user or an error and then let's just take what we have from article where we have the come on where did we put it we put it
all the way up here. Let's grab this, close and say a user and we return a domain validation error. Then we need to say params or we can actually just do it in line directly. That's probably easier. So we say row error and then we say queries, insert,
user pass dbtx and context and then we have to insert user params fill the struct id is going to be uid new like always data and a validated email and then for now we are simply just going to be passing the data
and then the password pair and then the password, because we need to hash this, but we haven't gotten there yet. So now we are actually returning a storing an empty string, a plain text string with the set we shouldn't do, but that's fine. We'll fix that in the next episode. So return row to user, row and null ever. Right. Nothing new happened here. We have done this many times before.
create a migration, we create some queries, we then create some wrapper functions in the model layer, and then we do some validation on the input data. So this is our models functions, but in the next episode, let's jump into how we actually hash our password, and we store it, and we pull it out, and we do all these operations that get into the nitty gritty of a password authentication.