(FEAT): Profile page is wired to backend.

This required service and repo creation for the users. Logging out is
not yet supported, but that is next.
This commit is contained in:
Hayden Hargreaves 2025-06-24 23:04:29 -07:00
parent a1f8fe60db
commit 6572c31ed4
8 changed files with 117 additions and 41 deletions

View File

@ -46,7 +46,12 @@ func GoogleCallback(ctx *gin.Context) {
false, // TODO: True in prod false, // TODO: True in prod
true, true,
) )
ctx.JSON(http.StatusOK, gin.H{"jwt": jwt, "googleUserInfo": googleUserInfo, "dbUser": dbUser})
// ctx.JSON(http.StatusOK, gin.H{"jwt": jwt, "googleUserInfo": googleUserInfo, "dbUser": dbUser})
_ = dbUser
_ = googleUserInfo
ctx.Redirect(http.StatusSeeOther, "/")
} }
} }

View File

@ -1,7 +1,10 @@
package handlers package handlers
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
layouts "github.com/haydenhargreaves/Potion/internal/templates/layouts" layouts "github.com/haydenhargreaves/Potion/internal/templates/layouts"
pages "github.com/haydenhargreaves/Potion/internal/templates/pages" pages "github.com/haydenhargreaves/Potion/internal/templates/pages"
) )
@ -35,8 +38,18 @@ func CreatePage(ctx *gin.Context) {
} }
func ProfilePage(ctx *gin.Context) { func ProfilePage(ctx *gin.Context) {
// If not logged in, direct to the login page
if !domain.IsLoggedIn(ctx) {
ctx.Redirect(http.StatusSeeOther, "/v1/web/login")
return
}
// Else, get the user data
deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
user := deps.UserService.GetAuthenicatedUser(ctx)
title := "Potion - Profile" title := "Potion - Profile"
page := pages.ProfilePage() page := pages.ProfilePage(user)
ctx.HTML(200, "", layouts.AppLayout(title, page)) ctx.HTML(200, "", layouts.AppLayout(title, page))
} }

View File

@ -1,6 +1,9 @@
package service package service
import domain "github.com/haydenhargreaves/Potion/internal/domain/user" import (
"github.com/gin-gonic/gin"
domain "github.com/haydenhargreaves/Potion/internal/domain/user"
)
// UserService implements the domain.UserService defined in the domain module. // UserService implements the domain.UserService defined in the domain module.
type UserService struct { type UserService struct {
@ -15,3 +18,18 @@ var _ domain.UserService = (*UserService)(nil)
func NewUserService(userRepository domain.UserRepository) domain.UserService { func NewUserService(userRepository domain.UserRepository) domain.UserService {
return &UserService{userRepository: userRepository} return &UserService{userRepository: userRepository}
} }
func (s *UserService) GetAuthenicatedUser(ctx *gin.Context) domain.User {
val, ok := ctx.Get("userId")
if !ok {
return domain.User{}
}
id := val.(int)
user, err := s.userRepository.GetUserById(id)
if err != nil {
return domain.User{}
}
return *user
}

View File

@ -3,4 +3,5 @@ package domain
type UserRepository interface { type UserRepository interface {
CreateGoogleUser(googleUserInfo *GoogleUserInfo, googleRefreshToken string) (User, error) CreateGoogleUser(googleUserInfo *GoogleUserInfo, googleRefreshToken string) (User, error)
GetGoogleUser(googleId string) (*User, error) GetGoogleUser(googleId string) (*User, error)
GetUserById(id int) (*User, error)
} }

View File

@ -1,4 +1,7 @@
package domain package domain
import "github.com/gin-gonic/gin"
type UserService interface { type UserService interface {
GetAuthenicatedUser(ctx *gin.Context) User
} }

View File

@ -105,3 +105,39 @@ func (r *UserRepository) GetGoogleUser(googleId string) (*domain.User, error) {
return &user, nil return &user, nil
} }
func (r *UserRepository) GetUserById(id int) (*domain.User, error) {
tx, err := r.db.Begin()
if err != nil {
tx.Rollback()
return nil, err
}
var user domain.User
query := `SELECT * FROM users WHERE id = $1`
if err := tx.QueryRow(query, id).Scan(
&user.Id,
&user.GoogleId,
&user.Name,
&user.Email,
&user.ImageUrl,
&user.GoogleRefreshToken,
&user.Created,
); err != nil {
// If no user was found, don't error, just return
if err == sql.ErrNoRows {
return nil, nil
}
tx.Rollback()
return nil, err
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return nil, err
}
return &user, nil
}

View File

@ -1,14 +1,16 @@
package templates package templates
import "github.com/haydenhargreaves/Potion/internal/templates/components" import "github.com/haydenhargreaves/Potion/internal/templates/components"
import "github.com/haydenhargreaves/Potion/internal/domain/user"
templ userDetailsSection(name, email, url string) { templ userDetailsSection(user domain.User) {
<section class="w-full flex flex-col justify-center my-8 py-4 border-b border-gray-300"> <section class="w-full flex flex-col justify-center my-8 py-4 border-b border-gray-300">
<div class="w-full p-4 md:p-8 flex items-center gap-x-8"> <div class="w-full p-4 md:p-8 flex items-center gap-x-8">
<img class="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none" src={ url }/> <img class="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none" src={
user.ImageUrl } />
<div class=""> <div class="">
<h1 class="text-md md:text-2xl font-semibold">{ name }</h1> <h1 class="text-md md:text-2xl font-semibold">{ user.Name }</h1>
<p class="text-xs md:text-sm">{ email }</p> <p class="text-xs md:text-sm">{ user.Email }</p>
</div> </div>
</div> </div>
</section> </section>
@ -17,21 +19,18 @@ templ userDetailsSection(name, email, url string) {
templ logoutSection() { templ logoutSection() {
<section class="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300"> <section class="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300">
<button <button
class="border border-red-500 text-red-500 w-9/10 md:w-1/3 py-2 rounded-lg hover:cursor-pointer hover:bg-red-100 duration-300" class="border border-red-500 text-red-500 w-9/10 md:w-1/3 py-2 rounded-lg hover:cursor-pointer hover:bg-red-100 duration-300">
>
Logout Logout
</button> </button>
</section> </section>
} }
templ ProfilePage() { templ ProfilePage(user domain.User) {
@components.Navbar(" profile") @components.Navbar(" profile")
<div class="w-full h-screen flex justify-center"> <div class="w-full h-screen flex justify-center">
<div <div
class="mx-2 md:mx-0 w-full md:w-1/2 md:pt-14 border-l border-r border-gray-300 bg-white flex flex-col justify-between" class="mx-2 md:mx-0 w-full md:w-1/2 md:pt-14 border-l border-r border-gray-300 bg-white flex flex-col justify-between">
> @userDetailsSection(user)
@userDetailsSection("Hayden Hargreaves", "hhargreaves2006@gmail.com",
"https://lh3.googleusercontent.com/a/ACg8ocLeT6ltjQIkiBy1MgMJDbQxtBfMVfn8sP4e1t7d0bCJeHFdpcea=s96-c")
@logoutSection() @logoutSection()
</div> </div>
</div> </div>

View File

@ -9,8 +9,9 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime" import templruntime "github.com/a-h/templ/runtime"
import "github.com/haydenhargreaves/Potion/internal/templates/components" import "github.com/haydenhargreaves/Potion/internal/templates/components"
import "github.com/haydenhargreaves/Potion/internal/domain/user"
func userDetailsSection(name, email, url string) templ.Component { func userDetailsSection(user domain.User) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -36,9 +37,10 @@ func userDetailsSection(name, email, url string) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(url) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(
user.ImageUrl)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 8, Col: 111} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 10, Col: 19}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -49,9 +51,9 @@ func userDetailsSection(name, email, url string) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(name) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 10, Col: 56} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 12, Col: 63}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -62,9 +64,9 @@ func userDetailsSection(name, email, url string) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(email) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 11, Col: 41} return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/pages/profile.templ`, Line: 13, Col: 48}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -107,7 +109,7 @@ func logoutSection() templ.Component {
}) })
} }
func ProfilePage() templ.Component { func ProfilePage(user domain.User) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -136,8 +138,7 @@ func ProfilePage() templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = userDetailsSection("Hayden Hargreaves", "hhargreaves2006@gmail.com", templ_7745c5c3_Err = userDetailsSection(user).Render(ctx, templ_7745c5c3_Buffer)
"https://lh3.googleusercontent.com/a/ACg8ocLeT6ltjQIkiBy1MgMJDbQxtBfMVfn8sP4e1t7d0bCJeHFdpcea=s96-c").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }