diff --git a/db/workout.go b/db/workout.go index 54c48ba..09ff1e4 100644 --- a/db/workout.go +++ b/db/workout.go @@ -5,13 +5,20 @@ import ( "me-fit/utils" "database/sql" + "errors" "time" "github.com/google/uuid" ) +var ( + ErrWorkoutNotExists = errors.New("Workout does not exist") +) + type DbWorkout interface { InsertWorkout(userId uuid.UUID, workout *WorkoutInsert) (*Workout, error) + GetWorkouts(userId uuid.UUID) ([]Workout, error) + DeleteWorkout(userId uuid.UUID, rowId int) error } type DbWorkoutSqlite struct { @@ -58,3 +65,46 @@ func (db DbWorkoutSqlite) InsertWorkout(userId uuid.UUID, workout *WorkoutInsert return NewWorkoutFromInsert(rowId, workout), nil } + +func (db DbWorkoutSqlite) GetWorkouts(userId uuid.UUID) ([]Workout, error) { + + rows, err := db.db.Query("SELECT rowid, date, type, sets, reps FROM workout WHERE user_id = ? ORDER BY date desc", userId) + if err != nil { + utils.LogError("Could not get workouts", err) + return nil, types.ErrInternal + } + + var workouts = make([]Workout, 0) + for rows.Next() { + var workout Workout + + err = rows.Scan(&workout.RowId, &workout.Date, &workout.Type, &workout.Sets, &workout.Reps) + if err != nil { + utils.LogError("Could not scan workout", err) + return nil, types.ErrInternal + } + + workouts = append(workouts, workout) + } + + return workouts, nil +} + +func (db DbWorkoutSqlite) DeleteWorkout(userId uuid.UUID, rowId int) error { + + res, err := db.db.Exec("DELETE FROM workout WHERE user_id = ? AND rowid = ?", userId, rowId) + if err != nil { + return types.ErrInternal + } + + rows, err := res.RowsAffected() + if err != nil { + return types.ErrInternal + } + + if rows == 0 { + return ErrWorkoutNotExists + } + + return nil +} diff --git a/handler/auth.go b/handler/auth.go index f1890d9..ef9fdab 100644 --- a/handler/auth.go +++ b/handler/auth.go @@ -43,9 +43,9 @@ func (handler HandlerAuthImpl) handle(router *http.ServeMux) { 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", service.HandleDeleteAccountComp(handler.db, handler.serverSettings)) - router.Handle("/api/auth/verify-resend", service.HandleVerifyResendComp(handler.db, handler.serverSettings)) - router.Handle("/api/auth/change-password", service.HandleChangePasswordComp(handler.db)) + router.Handle("/api/auth/delete-account", handler.HandleDeleteAccountComp()) + router.Handle("/api/auth/verify-resend", handler.HandleVerifyResendComp()) + router.Handle("/api/auth/change-password", handler.HandleChangePasswordComp()) router.Handle("/api/auth/reset-password", service.HandleResetPasswordComp(handler.db, handler.serverSettings)) router.Handle("/api/auth/reset-password-actual", service.HandleActualResetPasswordComp(handler.db)) } diff --git a/handler/default.go b/handler/default.go index 7242301..1579de9 100644 --- a/handler/default.go +++ b/handler/default.go @@ -16,18 +16,21 @@ func GetHandler(d *sql.DB, serverSettings *types.ServerSettings) http.Handler { randomGenerator := service.NewRandomGeneratorImpl() clock := service.NewClockImpl() dbAuth := db.NewDbAuthSqlite(d) + dbWorkout := db.NewDbWorkoutSqlite(d) mailService := service.NewMailServiceImpl(serverSettings) serviceAuth := service.NewServiceAuthImpl(dbAuth, randomGenerator, clock, mailService, serverSettings) + serviceWorkout := service.NewServiceWorkoutImpl(dbWorkout, randomGenerator, clock, mailService, serverSettings) handlerIndex := NewHandlerIndex(d, serviceAuth, serverSettings) handlerAuth := NewHandlerAuth(d, serviceAuth, serverSettings) + handlerWorkout := NewHandlerWorkout(d, serviceWorkout, serviceAuth, serverSettings) handlerIndex.handle(router) // Serve static files (CSS, JS and images) router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) - handleWorkout(d, router, serverSettings) + handlerWorkout.handle(router) handlerAuth.handle(router) diff --git a/handler/index_and_404.go b/handler/index_and_404.go index d5172d6..b7fb850 100644 --- a/handler/index_and_404.go +++ b/handler/index_and_404.go @@ -3,7 +3,6 @@ package handler import ( "me-fit/service" "me-fit/template" - "me-fit/template/auth" "me-fit/types" "me-fit/utils" @@ -32,10 +31,10 @@ func NewHandlerIndex(db *sql.DB, service service.ServiceAuth, serverSettings *ty } func (handler HandlerIndexImpl) handle(router *http.ServeMux) { - router.Handle("/", handler handleIndexAnd404()) + router.Handle("/", handler.handleIndexAnd404()) } -func (handler HanHandlerIndexImpl) handleIndexAnd404() http.HandlerFunc { +func (handler HandlerIndexImpl) handleIndexAnd404() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user, err := handler.service.GetUserFromSessionId(utils.GetSessionID(r)) diff --git a/handler/workout.go b/handler/workout.go index 69ec2cb..f2f06ec 100644 --- a/handler/workout.go +++ b/handler/workout.go @@ -1,15 +1,17 @@ package handler import ( + "log/slog" "me-fit/service" "me-fit/template" "me-fit/template/workout" "me-fit/types" "me-fit/utils" - "time" + "strconv" "database/sql" "net/http" + "time" ) type HandlerWorkout interface { @@ -35,8 +37,8 @@ func NewHandlerWorkout(db *sql.DB, service service.ServiceWorkout, auth service. func (handler HandlerWorkoutImpl) handle(router *http.ServeMux) { router.Handle("/workout", handler.handleWorkoutPage()) router.Handle("POST /api/workout", handler.handleAddWorkout()) - router.Handle("GET /api/workout", service.HandleWorkoutGetComp(handler.db)) - router.Handle("DELETE /api/workout/{id}", service.HandleWorkoutDeleteComp(handler.db)) + router.Handle("GET /api/workout", handler.handleGetWorkout()) + router.Handle("DELETE /api/workout/{id}", handler.handleDeleteWorkout()) } func (handler HandlerWorkoutImpl) handleWorkoutPage() http.HandlerFunc { @@ -88,3 +90,59 @@ func (handler HandlerWorkoutImpl) handleAddWorkout() http.HandlerFunc { } } } + +func (handler HandlerWorkoutImpl) handleGetWorkout() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, err := handler.auth.GetUserFromSessionId(utils.GetSessionID(r)) + if err != nil { + utils.DoRedirect(w, r, "/auth/signin") + return + } + + workouts, err := handler.service.GetWorkouts(user) + if err != nil { + return + } + + wos := make([]workout.Workout, 0) + for _, wo := range workouts { + wos = append(wos, workout.Workout{Id: wo.RowId, Date: wo.Date, Type: wo.Type, Sets: wo.Sets, Reps: wo.Reps}) + } + + workout.WorkoutListComp(wos).Render(r.Context(), w) + } +} + +func (handler HandlerWorkoutImpl) handleDeleteWorkout() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, err := handler.auth.GetUserFromSessionId(utils.GetSessionID(r)) + if err != nil { + utils.DoRedirect(w, r, "/auth/signin") + return + } + + rowId := r.PathValue("id") + if rowId == "" { + http.Error(w, "Missing required fields", http.StatusBadRequest) + slog.Warn("Missing required fields for workout delete") + utils.TriggerToast(w, r, "error", "Missing ID field") + return + } + + rowIdInt, err := strconv.Atoi(rowId) + if err != nil { + http.Error(w, "Invalid ID", http.StatusBadRequest) + slog.Warn("Invalid ID for workout delete") + utils.TriggerToast(w, r, "error", "Invalid ID") + return + } + + err = handler.service.DeleteWorkout(user, rowIdInt) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + slog.Error("Could not delete workout", err) + utils.TriggerToast(w, r, "error", "Internal Server Error") + return + } + } +} diff --git a/service/workout.go b/service/workout.go index 97f1c0f..ccd53f6 100644 --- a/service/workout.go +++ b/service/workout.go @@ -3,18 +3,16 @@ package service import ( "me-fit/db" "me-fit/types" - "me-fit/utils" - "database/sql" "errors" - "log/slog" - "net/http" "strconv" "time" ) type ServiceWorkout interface { AddWorkout(user *User, workoutDto *WorkoutDto) (*WorkoutDto, error) + DeleteWorkout(user *User, rowId int) error + GetWorkouts(user *User) ([]*WorkoutDto, error) } type ServiceWorkoutImpl struct { @@ -25,9 +23,9 @@ type ServiceWorkoutImpl struct { serverSettings *types.ServerSettings } -func NewServiceWorkoutImpl(dbAuth db.DbAuth, randomGenerator RandomGenerator, clock Clock, mailService MailService, serverSettings *types.ServerSettings) *ServiceAuthImpl { - return &ServiceAuthImpl{ - dbAuth: dbAuth, +func NewServiceWorkoutImpl(dbWorkout db.DbWorkout, randomGenerator RandomGenerator, clock Clock, mailService MailService, serverSettings *types.ServerSettings) ServiceWorkout { + return ServiceWorkoutImpl{ + dbWorkout: dbWorkout, randomGenerator: randomGenerator, clock: clock, mailService: mailService, @@ -66,7 +64,7 @@ var ( ErrInputValues = errors.New("Invalid input values") ) -func (service ServiceWorkoutImpl) AddWorkout(user *User, workoutDto WorkoutDto) (*WorkoutDto, error) { +func (service ServiceWorkoutImpl) AddWorkout(user *User, workoutDto *WorkoutDto) (*WorkoutDto, error) { if workoutDto.Date == "" || workoutDto.Type == "" || workoutDto.Sets == "" || workoutDto.Reps == "" { return nil, ErrInputValues @@ -97,45 +95,34 @@ func (service ServiceWorkoutImpl) AddWorkout(user *User, workoutDto WorkoutDto) return NewWorkoutDtoFromDb(workout), nil } -func HandleWorkoutDeleteComp(db *sql.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - user := utils.GetUser(r) - if user == nil { - utils.DoRedirect(w, r, "/auth/signin") - return - } - - rowId := r.PathValue("id") - if rowId == "" { - http.Error(w, "Missing required fields", http.StatusBadRequest) - slog.Warn("Missing required fields for workout delete") - utils.TriggerToast(w, r, "error", "Missing ID field") - return - } - - res, err := db.Exec("DELETE FROM workout WHERE user_id = ? AND rowid = ?", user.Id, rowId) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - utils.LogError("Could not delete workout", err) - utils.TriggerToast(w, r, "error", "Internal Server Error") - return - } - - rows, err := res.RowsAffected() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - utils.LogError("Could not get rows affected", err) - utils.TriggerToast(w, r, "error", "Internal Server Error") - return - } - - if rows == 0 { - http.Error(w, "Not found", http.StatusNotFound) - slog.Warn("Could not find workout to delete") - utils.TriggerToast(w, r, "error", "Not found. Refresh the page.") - return - } +func (service ServiceWorkoutImpl) DeleteWorkout(user *User, rowId int) error { + if user == nil { + return types.ErrInternal } + + return service.dbWorkout.DeleteWorkout(user.Id, rowId) +} + +func (service ServiceWorkoutImpl) GetWorkouts(user *User) ([]*WorkoutDto, error) { + if user == nil { + return nil, types.ErrInternal + } + + workouts, err := service.dbWorkout.GetWorkouts(user.Id) + if err != nil { + return nil, err + } + + // for _, workout := range workouts { + // workout.Date = renderDate(workout.Date) + // } + + workoutsDto := make([]*WorkoutDto, len(workouts)) + for i, workout := range workouts { + workoutsDto[i] = NewWorkoutDtoFromDb(&workout) + } + + return workoutsDto, nil } func renderDateStr(date string) (string, error) {