package main import ( "database/sql" "net/http" "strconv" "time" "github.com/labstack/echo/v4" "golang.org/x/crypto/bcrypt" ) type App struct { DB *sql.DB } // Login Handler func (app *App) LoginHandler(c echo.Context) error { var req LoginRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid request") } user, err := GetUserByUsername(app.DB, req.Username) if err != nil { return echo.NewHTTPError(http.StatusUnauthorized, "invalid credentials") } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil { return echo.NewHTTPError(http.StatusUnauthorized, "invalid credentials") } token, err := createToken(user.ID, user.Username, user.IsAdmin) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "error creating token") } response := LoginResponse{ Token: token, Username: user.Username, IsAdmin: user.IsAdmin, } return c.JSON(http.StatusOK, response) } // Schedule Handlers func (app *App) GetSchedulesHandler(c echo.Context) error { schedules, err := GetAllSchedules(app.DB) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, schedules) } func (app *App) CreateScheduleHandler(c echo.Context) error { var schedule Schedule if err := c.Bind(&schedule); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid request") } if err := CreateSchedule(app.DB, &schedule); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusCreated, map[string]string{"message": "schedule created"}) } func (app *App) DeleteScheduleHandler(c echo.Context) error { id, err := strconv.Atoi(c.QueryParam("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid id") } if err := DeleteSchedule(app.DB, id); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } // // User Handlers // func (app *App) CreateUserHandler(c echo.Context) error { // var req CreateUserRequest // if err := c.Bind(&req); err != nil { // return echo.NewHTTPError(http.StatusBadRequest, "invalid request") // } // hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) // if err != nil { // return echo.NewHTTPError(http.StatusInternalServerError, "error hashing password") // } // if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin); err != nil { // return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) // } // return c.JSON(http.StatusCreated, map[string]string{"message": "user created"}) // } func (app *App) GetUsersHandler(c echo.Context) error { users, err := GetAllUsers(app.DB) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, users) } func (app *App) DeleteUserHandler(c echo.Context) error { id, err := strconv.Atoi(c.QueryParam("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid id") } if err := DeleteUser(app.DB, id); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } // Time Entry Handlers func (app *App) CreateTimeEntryHandler(c echo.Context) error { userID := c.Get("user_id").(int) var entry TimeEntry if err := c.Bind(&entry); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid request") } entry.UserID = userID if err := CreateTimeEntry(app.DB, &entry); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusCreated, map[string]string{"message": "time entry created"}) } func (app *App) GetMyTimeEntriesHandler(c echo.Context) error { userID := c.Get("user_id").(int) entries, err := GetTimeEntriesByUser(app.DB, userID) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, entries) } func (app *App) GetWeekDates(c echo.Context) error { year, err := strconv.Atoi(c.QueryParam("year")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid year") } week, err := strconv.Atoi(c.QueryParam("week")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid week") } dates := calculateWeekDates(year, week) return c.JSON(http.StatusOK, dates) } func (app *App) CheckWeekHasEntries(c echo.Context) error { userID := c.Get("user_id").(int) year, err := strconv.Atoi(c.QueryParam("year")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid year") } week, err := strconv.Atoi(c.QueryParam("week")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid week") } hasEntries, err := CheckUserHasEntriesForWeek(app.DB, userID, year, week) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, map[string]bool{"has_entries": hasEntries}) } func (app *App) GetAllTimeEntriesHandler(c echo.Context) error { entries, err := GetAllTimeEntries(app.DB) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, entries) } func (app *App) GetWeeklyHoursHandler(c echo.Context) error { hours, err := GetWeeklyHours(app.DB) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.JSON(http.StatusOK, hours) } func (app *App) DeleteWeekEntries(c echo.Context) error { userID := c.Get("user_id").(int) year, err := strconv.Atoi(c.QueryParam("year")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid year") } week, err := strconv.Atoi(c.QueryParam("week")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid week") } if err := DeleteTimeEntriesByUserAndWeek(app.DB, userID, year, week); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } type WeekDates struct { Year int `json:"year"` Week int `json:"week"` Dates map[string]string `json:"dates"` Range string `json:"range"` } func calculateWeekDates(year, week int) WeekDates { jan4 := time.Date(year, time.January, 4, 0, 0, 0, 0, time.UTC) weekday := int(jan4.Weekday()) if weekday == 0 { weekday = 7 } daysToMonday := weekday - 1 mondayWeek1 := jan4.AddDate(0, 0, -daysToMonday) targetMonday := mondayWeek1.AddDate(0, 0, (week-1)*7) dates := make(map[string]string) weekDays := []string{"0", "1", "2", "3", "4"} var firstDate, lastDate time.Time for i, day := range weekDays { date := targetMonday.AddDate(0, 0, i) dates[day] = date.Format("2006-01-02") if i == 0 { firstDate = date } if i == 4 { lastDate = date } } rangeStr := firstDate.Format("2006-01-02") + " bis " + lastDate.Format("2006-01-02") return WeekDates{ Year: year, Week: week, Dates: dates, Range: rangeStr, } } type BatchTimeEntryRequest struct { Entries []struct { ScheduleID int `json:"schedule_id"` Date string `json:"date"` Type string `json:"type"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` } `json:"entries"` } func (app *App) CreateBatchTimeEntriesHandler(c echo.Context) error { userID := c.Get("user_id").(int) var req BatchTimeEntryRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid request") } tx, err := app.DB.Begin() if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "transaction error") } defer tx.Rollback() stmt, err := tx.Prepare("INSERT INTO time_entries (user_id, schedule_id, date, type, start_time, end_time) VALUES (?, ?, ?, ?, ?, ?)") if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "prepare error") } defer stmt.Close() for _, entry := range req.Entries { _, err := stmt.Exec(userID, entry.ScheduleID, entry.Date, entry.Type, entry.StartTime, entry.EndTime) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "insert error") } } if err := tx.Commit(); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "commit error") } return c.JSON(http.StatusCreated, map[string]string{"message": "entries created"}) } func (app *App) UpdateUserHandler(c echo.Context) error { userID, err := strconv.Atoi(c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid user ID") } var req UpdateUserRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := UpdateUser(app.DB, userID, req.WeeklyHours); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } func (app *App) ResetPasswordHandler(c echo.Context) error { userID, err := strconv.Atoi(c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid user ID") } var req ResetPasswordRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Error hashing password") } if err := ResetUserPassword(app.DB, userID, string(hashedPassword)); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } func (app *App) UpdateTimeEntryHandler(c echo.Context) error { entryID, err := strconv.Atoi(c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid entry ID") } var req UpdateTimeEntryRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := UpdateTimeEntry(app.DB, entryID, req.Date, req.StartTime, req.EndTime, req.Type); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } func (app *App) DeleteTimeEntryHandler(c echo.Context) error { entryID, err := strconv.Atoi(c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid entry ID") } if err := DeleteTimeEntry(app.DB, entryID); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusOK) } func (app *App) GetMyWeeklySummaryHandler(c echo.Context) error { userID := c.Get("user_id").(int) year, err := strconv.Atoi(c.QueryParam("year")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid year") } week, err := strconv.Atoi(c.QueryParam("week")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Invalid week") } user, err := GetUserByID(app.DB, userID) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } allHours, err := GetWeeklyHours(app.DB) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } for _, h := range allHours { if h.UserID == userID && h.Year == year && h.Week == week { return c.JSON(http.StatusOK, h) } } return c.JSON(http.StatusOK, WeeklyHours{ UserID: userID, Username: user.Username, Year: year, Week: week, TotalHours: 0, ExpectedHours: user.WeeklyHours, RemainingHours: user.WeeklyHours, }) } func (app *App) CreateUserHandler(c echo.Context) error { var req CreateUserRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Error hashing password") } if req.WeeklyHours == 0 { req.WeeklyHours = 40.0 } if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin, req.WeeklyHours); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } return c.NoContent(http.StatusCreated) }