444 lines
12 KiB
Go
444 lines
12 KiB
Go
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)
|
|
}
|