fix: extract html rendering #291

Merged
tim merged 1 commits from extract-html-rendering into prod 2024-12-04 20:13:14 +00:00
6 changed files with 138 additions and 145 deletions

View File

@@ -2,7 +2,6 @@ package handler
import (
"me-fit/service"
"me-fit/template"
"me-fit/template/auth"
"me-fit/types"
"me-fit/utils"
@@ -19,31 +18,36 @@ type HandlerAuth interface {
type HandlerAuthImpl struct {
service service.AuthService
serverSettings *types.ServerSettings
render *Render
}
func NewHandlerAuth(service service.AuthService, serverSettings *types.ServerSettings) HandlerAuth {
func NewHandlerAuth(service service.AuthService, render *Render) HandlerAuth {
return HandlerAuthImpl{
service: service,
serverSettings: serverSettings,
render: render,
}
}
func (handler HandlerAuthImpl) Handle(router *http.ServeMux) {
// Don't use auth middleware for these routes, as it makes redirecting very difficult, if the mail is not yet verified
router.Handle("/auth/signin", handler.handleSignInPage())
router.Handle("/auth/signup", handler.handleSignUpPage())
router.Handle("/auth/verify", handler.handleSignUpVerifyPage()) // Hint for the user to verify their email
router.Handle("/auth/delete-account", handler.handleDeleteAccountPage())
router.Handle("/auth/verify-email", handler.HandleSignUpVerifyResponsePage()) // The link contained in the email
router.Handle("/auth/change-password", handler.handleChangePasswordPage())
router.Handle("/auth/reset-password", handler.handleResetPasswordPage())
router.Handle("/api/auth/signup", handler.handleSignUp())
router.Handle("/api/auth/signin", handler.handleSignIn())
router.Handle("/api/auth/signout", handler.handleSignOut())
router.Handle("/api/auth/delete-account", handler.HandleDeleteAccountComp())
router.Handle("/auth/verify", handler.handleSignUpVerifyPage())
router.Handle("/api/auth/verify-resend", handler.HandleVerifyResendComp())
router.Handle("/auth/verify-email", handler.HandleSignUpVerifyResponsePage())
router.Handle("/api/auth/signup", handler.handleSignUp())
router.Handle("/auth/signin", handler.handleSignInPage())
router.Handle("/api/auth/signin", handler.handleSignIn())
router.Handle("/api/auth/signout", handler.handleSignOut())
router.Handle("/auth/delete-account", handler.handleDeleteAccountPage())
router.Handle("/api/auth/delete-account", handler.HandleDeleteAccountComp())
router.Handle("/auth/change-password", handler.handleChangePasswordPage())
router.Handle("/api/auth/change-password", handler.HandleChangePasswordComp())
router.Handle("/auth/reset-password", handler.handleResetPasswordPage())
router.Handle("/api/auth/reset-password", handler.HandleForgotPasswordComp())
router.Handle("/api/auth/reset-password-actual", handler.HandleForgotPasswordResponseComp())
}
@@ -54,24 +58,19 @@ var (
func (handler HandlerAuthImpl) handleSignInPage() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, err := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if err != nil {
userComp := service.UserInfoComp(nil)
signIn := auth.SignInOrUpComp(true)
err := template.Layout(signIn, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render sign in page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
user, _ := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if user != nil {
if !user.EmailVerified {
utils.DoRedirect(w, r, "/auth/verify")
} else {
utils.DoRedirect(w, r, "/")
}
return
}
comp := auth.SignInOrUpComp(true)
handler.render.RenderLayout(r, w, comp, nil)
}
}
@@ -122,23 +121,19 @@ func (handler HandlerAuthImpl) handleSignIn() http.HandlerFunc {
func (handler HandlerAuthImpl) handleSignUpPage() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, err := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if err != nil {
userComp := service.UserInfoComp(nil)
signUpComp := auth.SignInOrUpComp(false)
err := template.Layout(signUpComp, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render sign up page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
user, _ := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if user != nil {
if !user.EmailVerified {
utils.DoRedirect(w, r, "/auth/verify")
} else {
utils.DoRedirect(w, r, "/")
}
return
}
signUpComp := auth.SignInOrUpComp(false)
handler.render.RenderLayout(r, w, signUpComp, nil)
}
}
@@ -198,22 +193,19 @@ func (handler HandlerAuthImpl) handleSignOut() http.HandlerFunc {
func (handler HandlerAuthImpl) handleSignUpVerifyPage() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, err := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if err != nil {
user, _ := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if user == nil {
utils.DoRedirect(w, r, "/auth/signin")
return
}
if user.EmailVerified {
utils.DoRedirect(w, r, "/")
} else {
userComp := service.UserInfoComp(user)
return
}
signIn := auth.VerifyComp()
err := template.Layout(signIn, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render verify page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
handler.render.RenderLayout(r, w, signIn, user)
}
}
@@ -225,13 +217,8 @@ func (handler HandlerAuthImpl) handleDeleteAccountPage() http.HandlerFunc {
utils.DoRedirect(w, r, "/auth/signin")
}
userComp := service.UserInfoComp(user)
comp := auth.DeleteAccountComp()
err = template.Layout(comp, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render delete account page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -244,15 +231,11 @@ func (handler HandlerAuthImpl) handleChangePasswordPage() http.HandlerFunc {
if user == nil && !isPasswordReset {
utils.DoRedirect(w, r, "/auth/signin")
} else {
userComp := service.UserInfoComp(user)
return
}
comp := auth.ChangePasswordComp(isPasswordReset)
err := template.Layout(comp, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render change password page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -262,15 +245,11 @@ func (handler HandlerAuthImpl) handleResetPasswordPage() http.HandlerFunc {
user, err := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
if err != nil {
utils.DoRedirect(w, r, "/auth/signin")
return
}
userComp := service.UserInfoComp(user)
comp := auth.ResetPasswordComp()
err = template.Layout(comp, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render change password page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -283,13 +262,8 @@ func (handler HandlerAuthImpl) HandleResetPasswordPage() http.HandlerFunc {
return
}
userComp := service.UserInfoComp(user)
comp := auth.ResetPasswordComp()
err = template.Layout(comp, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render change password page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -382,7 +356,7 @@ func (handler HandlerAuthImpl) HandleForgotPasswordComp() http.HandlerFunc {
return
}
err := handler.service.ForgotPassword(email)
err := handler.service.SendForgotPasswordMail(email)
if err != nil {
utils.TriggerToast(w, r, "error", "Internal Server Error")
} else {
@@ -409,7 +383,7 @@ func (handler HandlerAuthImpl) HandleForgotPasswordResponseComp() http.HandlerFu
newPass := r.FormValue("new-password")
err = handler.service.ForgotPasswordResponse(token, newPass)
err = handler.service.ForgotPassword(token, newPass)
if err != nil {
utils.TriggerToast(w, r, "error", err.Error())
} else {

View File

@@ -3,7 +3,6 @@ package handler
import (
"me-fit/service"
"me-fit/template"
"me-fit/types"
"me-fit/utils"
"net/http"
@@ -17,13 +16,13 @@ type IndexHandler interface {
type IndexHandlerImpl struct {
service service.AuthService
serverSettings *types.ServerSettings
render *Render
}
func NewIndexHandler(service service.AuthService, serverSettings *types.ServerSettings) IndexHandler {
func NewIndexHandler(service service.AuthService, render *Render) IndexHandler {
return IndexHandlerImpl{
service: service,
serverSettings: serverSettings,
render: render,
}
}
@@ -35,20 +34,15 @@ func (handler IndexHandlerImpl) handleIndexAnd404() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, _ := handler.service.GetUserFromSessionId(utils.GetSessionID(r))
var comp templ.Component = nil
userComp := service.UserInfoComp(user)
var comp templ.Component
if r.URL.Path != "/" {
comp = template.Layout(template.NotFound(), userComp, handler.serverSettings.Environment)
comp = template.NotFound()
w.WriteHeader(http.StatusNotFound)
} else {
comp = template.Layout(template.Index(), userComp, handler.serverSettings.Environment)
comp = template.Index()
}
err := comp.Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render index", err)
http.Error(w, "Failed to render index", http.StatusInternalServerError)
}
handler.render.RenderLayout(r, w, comp, user)
}
}

47
handler/render.go Normal file
View File

@@ -0,0 +1,47 @@
package handler
import (
"me-fit/service"
"me-fit/template"
"me-fit/template/auth"
"me-fit/types"
"me-fit/utils"
"net/http"
"github.com/a-h/templ"
)
type Render struct {
serverSettings *types.ServerSettings
}
func NewRender(serverSettings *types.ServerSettings) *Render {
return &Render{
serverSettings: serverSettings,
}
}
func (render *Render) Render(r *http.Request, w http.ResponseWriter, comp templ.Component) {
err := comp.Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render layout", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
func (render *Render) RenderLayout(r *http.Request, w http.ResponseWriter, slot templ.Component, user *service.User) {
userComp := render.getUserComp(user)
layout := template.Layout(slot, userComp, render.serverSettings.Environment)
render.Render(r, w, layout)
}
func (render *Render) getUserComp(user *service.User) templ.Component {
if user != nil {
return auth.UserComp(user.Email)
} else {
return auth.UserComp("")
}
}

View File

@@ -2,9 +2,7 @@ package handler
import (
"me-fit/service"
"me-fit/template"
"me-fit/template/workout"
"me-fit/types"
"me-fit/utils"
"log/slog"
@@ -20,14 +18,14 @@ type WorkoutHandler interface {
type WorkoutHandlerImpl struct {
service service.WorkoutService
auth service.AuthService
serverSettings *types.ServerSettings
render *Render
}
func NewWorkoutHandler(service service.WorkoutService, auth service.AuthService, serverSettings *types.ServerSettings) WorkoutHandler {
func NewWorkoutHandler(service service.WorkoutService, auth service.AuthService, render *Render) WorkoutHandler {
return WorkoutHandlerImpl{
service: service,
auth: auth,
serverSettings: serverSettings,
render: render,
}
}
@@ -47,13 +45,8 @@ func (handler WorkoutHandlerImpl) handleWorkoutPage() http.HandlerFunc {
}
currentDate := time.Now().Format("2006-01-02")
inner := workout.WorkoutComp(currentDate)
userComp := service.UserInfoComp(user)
err = template.Layout(inner, userComp, handler.serverSettings.Environment).Render(r.Context(), w)
if err != nil {
utils.LogError("Failed to render workout page", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
comp := workout.WorkoutComp(currentDate)
handler.render.RenderLayout(r, w, comp, user)
}
}
@@ -79,12 +72,8 @@ func (handler WorkoutHandlerImpl) handleAddWorkout() http.HandlerFunc {
}
wor := workout.Workout{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps}
err = workout.WorkoutItemComp(wor, true).Render(r.Context(), w)
if err != nil {
utils.LogError("Could not render workoutitem", err)
utils.TriggerToast(w, r, "error", "Internal Server Error")
http.Error(w, err.Error(), http.StatusInternalServerError)
}
comp := workout.WorkoutItemComp(wor, true)
handler.render.Render(r, w, comp)
}
}
@@ -106,12 +95,8 @@ func (handler WorkoutHandlerImpl) handleGetWorkout() http.HandlerFunc {
wos = append(wos, workout.Workout{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps})
}
err = workout.WorkoutListComp(wos).Render(r.Context(), w)
if err != nil {
utils.LogError("Could not render workoutlist", err)
utils.TriggerToast(w, r, "error", "Internal Server Error")
http.Error(w, err.Error(), http.StatusInternalServerError)
}
comp := workout.WorkoutListComp(wos)
handler.render.Render(r, w, comp)
}
}

View File

@@ -112,12 +112,14 @@ func createHandler(d *sql.DB, serverSettings *types.ServerSettings) http.Handler
randomService := service.NewRandomServiceImpl()
clockService := service.NewClockServiceImpl()
mailService := service.NewMailServiceImpl(serverSettings)
authService := service.NewAuthServiceImpl(authDb, randomService, clockService, mailService, serverSettings)
workoutService := service.NewWorkoutServiceImpl(workoutDb, randomService, clockService, mailService, serverSettings)
indexHandler := handler.NewIndexHandler(authService, serverSettings)
authHandler := handler.NewHandlerAuth(authService, serverSettings)
workoutHandler := handler.NewWorkoutHandler(workoutService, authService, serverSettings)
render := handler.NewRender(serverSettings)
indexHandler := handler.NewIndexHandler(authService, render)
authHandler := handler.NewHandlerAuth(authService, render)
workoutHandler := handler.NewWorkoutHandler(workoutService, authService, render)
indexHandler.Handle(router)

View File

@@ -9,12 +9,10 @@ import (
"time"
"me-fit/db"
"me-fit/template/auth"
mailTemplate "me-fit/template/mail"
"me-fit/types"
"me-fit/utils"
"github.com/a-h/templ"
"github.com/google/uuid"
"golang.org/x/crypto/argon2"
)
@@ -56,15 +54,19 @@ func NewSession(session *db.Session, user *User) *Session {
}
type AuthService interface {
SignIn(email string, password string) (*Session, error)
SignUp(email string, password string) (*User, error)
SendVerificationMail(userId uuid.UUID, email string)
VerifyUserEmail(token string) error
SignIn(email string, password string) (*Session, error)
SignOut(sessionId string) error
DeleteAccount(user *User) error
ChangePassword(user *User, currPass, newPass string) error
ForgotPassword(email string) error
ForgotPasswordResponse(token string, newPass string) error
SendForgotPasswordMail(email string) error
ForgotPassword(token string, newPass string) error
GetUserFromSessionId(sessionId string) (*User, error)
}
@@ -271,17 +273,6 @@ func (service AuthServiceImpl) GetUserFromSessionId(sessionId string) (*User, er
}
}
// TODO
func UserInfoComp(user *User) templ.Component {
if user != nil {
return auth.UserComp(user.Email)
} else {
return auth.UserComp("")
}
}
func (service AuthServiceImpl) DeleteAccount(user *User) error {
err := service.dbAuth.DeleteUser(user.Id)
@@ -326,7 +317,7 @@ func (service AuthServiceImpl) ChangePassword(user *User, currPass, newPass stri
return nil
}
func (service AuthServiceImpl) ForgotPassword(email string) error {
func (service AuthServiceImpl) SendForgotPasswordMail(email string) error {
tokenStr, err := service.randomGenerator.String(32)
if err != nil {
@@ -360,7 +351,7 @@ func (service AuthServiceImpl) ForgotPassword(email string) error {
return nil
}
func (service AuthServiceImpl) ForgotPasswordResponse(tokenStr string, newPass string) error {
func (service AuthServiceImpl) ForgotPassword(tokenStr string, newPass string) error {
if !isPasswordValid(newPass) {
return ErrInvalidPassword