(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
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
import (
"net/http"
"github.com/gin-gonic/gin"
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
layouts "github.com/haydenhargreaves/Potion/internal/templates/layouts"
pages "github.com/haydenhargreaves/Potion/internal/templates/pages"
)
@ -35,8 +38,18 @@ func CreatePage(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"
page := pages.ProfilePage()
page := pages.ProfilePage(user)
ctx.HTML(200, "", layouts.AppLayout(title, page))
}

View File

@ -1,6 +1,9 @@
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.
type UserService struct {
@ -15,3 +18,18 @@ var _ domain.UserService = (*UserService)(nil)
func NewUserService(userRepository domain.UserRepository) domain.UserService {
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 {
CreateGoogleUser(googleUserInfo *GoogleUserInfo, googleRefreshToken string) (User, error)
GetGoogleUser(googleId string) (*User, error)
GetUserById(id int) (*User, error)
}

View File

@ -1,4 +1,7 @@
package domain
import "github.com/gin-gonic/gin"
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
}
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,38 +1,37 @@
package templates
import "github.com/haydenhargreaves/Potion/internal/templates/components"
import "github.com/haydenhargreaves/Potion/internal/domain/user"
templ userDetailsSection(name, email, url string) {
<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">
<img class="w-24 md:w-32 border-2 border-blue-500 rounded-full shadow-blue-500 shadow select-none" src={ url }/>
<div class="">
<h1 class="text-md md:text-2xl font-semibold">{ name }</h1>
<p class="text-xs md:text-sm">{ email }</p>
</div>
</div>
</section>
templ userDetailsSection(user domain.User) {
<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">
<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="">
<h1 class="text-md md:text-2xl font-semibold">{ user.Name }</h1>
<p class="text-xs md:text-sm">{ user.Email }</p>
</div>
</div>
</section>
}
templ logoutSection() {
<section class="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300">
<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"
>
Logout
</button>
</section>
<section class="w-full flex flex-col justify-center items-center py-8 border-t border-gray-300">
<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">
Logout
</button>
</section>
}
templ ProfilePage() {
@components.Navbar(" profile")
<div class="w-full h-screen flex justify-center">
<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"
>
@userDetailsSection("Hayden Hargreaves", "hhargreaves2006@gmail.com",
"https://lh3.googleusercontent.com/a/ACg8ocLeT6ltjQIkiBy1MgMJDbQxtBfMVfn8sP4e1t7d0bCJeHFdpcea=s96-c")
@logoutSection()
</div>
</div>
templ ProfilePage(user domain.User) {
@components.Navbar(" profile")
<div class="w-full h-screen flex justify-center">
<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">
@userDetailsSection(user)
@logoutSection()
</div>
</div>
}

View File

@ -9,8 +9,9 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
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) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
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
}
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 {
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))
if templ_7745c5c3_Err != nil {
@ -49,9 +51,9 @@ func userDetailsSection(name, email, url string) templ.Component {
return templ_7745c5c3_Err
}
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 {
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))
if templ_7745c5c3_Err != nil {
@ -62,9 +64,9 @@ func userDetailsSection(name, email, url string) templ.Component {
return templ_7745c5c3_Err
}
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 {
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))
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) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -136,8 +138,7 @@ func ProfilePage() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = userDetailsSection("Hayden Hargreaves", "hhargreaves2006@gmail.com",
"https://lh3.googleusercontent.com/a/ACg8ocLeT6ltjQIkiBy1MgMJDbQxtBfMVfn8sP4e1t7d0bCJeHFdpcea=s96-c").Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = userDetailsSection(user).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}