old entries have not been deleted, before new entries have been added. This has been fixed. Also manual entries by administrators are know protected and can only be deleted by an administrator.
728 lines
18 KiB
Go
728 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/labstack/echo/v4"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type App struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
func HandleError(c echo.Context, err *AppError) error {
|
|
log.Printf("[%s] %s", err.Code, err.Error())
|
|
|
|
return c.JSON(err.HTTPStatus, err.ToResponse())
|
|
}
|
|
|
|
func getClaims(c echo.Context) (*Claims, error) {
|
|
user, ok := c.Get("user").(*jwt.Token)
|
|
if !ok {
|
|
return nil, fmt.Errorf("JWT token missing or invalid")
|
|
}
|
|
|
|
claims, ok := user.Claims.(*Claims)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to parse JWT claims")
|
|
}
|
|
|
|
return claims, nil
|
|
}
|
|
|
|
func isDuplicateError(err error) bool {
|
|
return err != nil && (err.Error() == "UNIQUE constraint failed" ||
|
|
strings.Contains(err.Error(), "UNIQUE") ||
|
|
strings.Contains(err.Error(), "duplicate"))
|
|
}
|
|
|
|
func (app *App) LoginHandler(c echo.Context) error {
|
|
var req LoginRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Login-Daten"))
|
|
}
|
|
|
|
if req.Username == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Benutzername"))
|
|
}
|
|
if req.Password == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Passwort"))
|
|
}
|
|
|
|
user, err := GetUserByUsername(app.DB, req.Username)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrInvalidCredentialsMsg())
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
|
return HandleError(c, ErrInvalidCredentialsMsg())
|
|
}
|
|
|
|
token, err := createToken(user.ID, user.Username, user.IsAdmin)
|
|
if err != nil {
|
|
return HandleError(c, ErrInternalMsg(err))
|
|
}
|
|
|
|
response := LoginResponse{
|
|
Token: token,
|
|
Username: user.Username,
|
|
IsAdmin: user.IsAdmin,
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (app *App) GetSchedulesHandler(c echo.Context) error {
|
|
schedules, err := GetAllSchedules(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
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 HandleError(c, ErrInvalidInputMsg("Stundenplan-Daten"))
|
|
}
|
|
|
|
if schedule.StartTime == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Startzeit"))
|
|
}
|
|
if schedule.EndTime == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Endzeit"))
|
|
}
|
|
if schedule.Title == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Titel"))
|
|
}
|
|
|
|
if err := CreateSchedule(app.DB, &schedule); err != nil {
|
|
if isDuplicateError(err) {
|
|
return HandleError(c, ErrAlreadyExistsMsg("Stundenplan-Eintrag"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, map[string]string{"message": "Stundenplan erstellt"})
|
|
}
|
|
|
|
func (app *App) DeleteScheduleHandler(c echo.Context) error {
|
|
id, err := strconv.Atoi(c.QueryParam("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Stundenplan-ID"))
|
|
}
|
|
|
|
if err := DeleteSchedule(app.DB, id); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Stundenplan"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (app *App) GetYearlyHoursSummaryHandler(c echo.Context) error {
|
|
hours, err := GetYearlyHoursSummary(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if hours == nil {
|
|
hours = []WeeklyHours{}
|
|
}
|
|
return c.JSON(http.StatusOK, hours)
|
|
}
|
|
|
|
func (app *App) AdminCreateTimeEntryHandler(c echo.Context) error {
|
|
var req struct {
|
|
UserID int `json:"user_id"`
|
|
Date string `json:"date"`
|
|
Hours float64 `json:"hours"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Zeiteintrag-Daten"))
|
|
}
|
|
|
|
if req.UserID == 0 {
|
|
return HandleError(c, ErrMissingFieldMsg("Benutzer"))
|
|
}
|
|
if req.Date == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Datum"))
|
|
}
|
|
if req.Hours == 0 {
|
|
return HandleError(c, ErrMissingFieldMsg("Stunden"))
|
|
}
|
|
|
|
entry := TimeEntry{
|
|
UserID: req.UserID,
|
|
Date: req.Date,
|
|
StartTime: "00:00",
|
|
EndTime: "00:00",
|
|
Type: "manual",
|
|
}
|
|
|
|
if err := CreateManualTimeEntry(app.DB, &entry, req.Hours); err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (app *App) GetUsersHandler(c echo.Context) error {
|
|
users, err := GetAllUsers(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if users == nil {
|
|
users = []User{}
|
|
}
|
|
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 HandleError(c, ErrInvalidInputMsg("Benutzer-ID"))
|
|
}
|
|
|
|
if id == 1 {
|
|
return HandleError(c, ErrProtectedUserMsg())
|
|
}
|
|
|
|
if err := DeleteUser(app.DB, id); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Benutzer"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (app *App) CreateTimeEntryHandler(c echo.Context) error {
|
|
claims, err := getClaims(c)
|
|
if err != nil {
|
|
return HandleError(c, ErrUnauthorizedMsg())
|
|
}
|
|
|
|
var entry TimeEntry
|
|
if err := c.Bind(&entry); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Zeiteintrag-Daten"))
|
|
}
|
|
|
|
if entry.Date == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Datum"))
|
|
}
|
|
if entry.StartTime == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Startzeit"))
|
|
}
|
|
if entry.EndTime == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Endzeit"))
|
|
}
|
|
|
|
entry.UserID = claims.UserID
|
|
|
|
if err := CreateTimeEntry(app.DB, &entry); err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, map[string]string{"message": "Zeiteintrag erstellt"})
|
|
}
|
|
|
|
func (app *App) GetMyTimeEntriesHandler(c echo.Context) error {
|
|
claims, err := getClaims(c)
|
|
if err != nil {
|
|
return HandleError(c, ErrUnauthorizedMsg())
|
|
}
|
|
|
|
entries, err := GetTimeEntriesByUser(app.DB, claims.UserID)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if entries == nil {
|
|
entries = []TimeEntry{}
|
|
}
|
|
|
|
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 HandleError(c, ErrInvalidInputMsg("Jahr"))
|
|
}
|
|
|
|
week, err := strconv.Atoi(c.QueryParam("week"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Woche"))
|
|
}
|
|
|
|
if week < 1 || week > 53 {
|
|
return HandleError(c, ErrInvalidInputMsg("Woche (muss zwischen 1 und 53 liegen)"))
|
|
}
|
|
|
|
dates := calculateWeekDates(year, week)
|
|
return c.JSON(http.StatusOK, dates)
|
|
}
|
|
|
|
func (app *App) CheckWeekHasEntries(c echo.Context) error {
|
|
claims, err := getClaims(c)
|
|
if err != nil {
|
|
return HandleError(c, ErrUnauthorizedMsg())
|
|
}
|
|
|
|
year, err := strconv.Atoi(c.QueryParam("year"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Jahr"))
|
|
}
|
|
|
|
week, err := strconv.Atoi(c.QueryParam("week"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Woche"))
|
|
}
|
|
|
|
hasEntries, err := CheckUserHasEntriesForWeek(app.DB, claims.UserID, year, week)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
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 HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if entries == nil {
|
|
entries = []TimeEntry{}
|
|
}
|
|
return c.JSON(http.StatusOK, entries)
|
|
}
|
|
|
|
func (app *App) GetWeeklyHoursHandler(c echo.Context) error {
|
|
hours, err := GetWeeklyHours(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if hours == nil {
|
|
hours = []WeeklyHours{}
|
|
}
|
|
return c.JSON(http.StatusOK, hours)
|
|
}
|
|
|
|
func (app *App) DeleteWeekEntries(c echo.Context) error {
|
|
claims, err := getClaims(c)
|
|
if err != nil {
|
|
return HandleError(c, ErrUnauthorizedMsg())
|
|
}
|
|
|
|
year, err := strconv.Atoi(c.QueryParam("year"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Jahr"))
|
|
}
|
|
|
|
week, err := strconv.Atoi(c.QueryParam("week"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Woche"))
|
|
}
|
|
|
|
if err := DeleteNonManualTimeEntriesByUserAndWeek(app.DB, claims.UserID, year, week); err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
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 {
|
|
claims, err := getClaims(c)
|
|
if err != nil {
|
|
return HandleError(c, ErrUnauthorizedMsg())
|
|
}
|
|
|
|
var req BatchTimeEntryRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Zeiteintrag-Daten"))
|
|
}
|
|
|
|
if len(req.Entries) == 0 {
|
|
return HandleError(c, ErrMissingFieldMsg("Zeiteinträge"))
|
|
}
|
|
|
|
if len(req.Entries) > 0 {
|
|
firstDate := req.Entries[0].Date
|
|
t, err := time.Parse("2006-01-02", firstDate)
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Datum-Format"))
|
|
}
|
|
year, week := t.ISOWeek()
|
|
|
|
if err := DeleteNonManualTimeEntriesByUserAndWeek(app.DB, claims.UserID, year, week); err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
}
|
|
|
|
tx, err := app.DB.Begin()
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
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 HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
defer stmt.Close()
|
|
|
|
for _, entry := range req.Entries {
|
|
_, err := stmt.Exec(claims.UserID, entry.ScheduleID, entry.Date, entry.Type, entry.StartTime, entry.EndTime)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, map[string]string{"message": "Zeiteinträge erstellt"})
|
|
}
|
|
|
|
func (app *App) UpdateUserHandler(c echo.Context) error {
|
|
userID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Benutzer-ID"))
|
|
}
|
|
|
|
if userID == 1 {
|
|
return HandleError(c, ErrProtectedUserMsg())
|
|
}
|
|
|
|
var req UpdateUserRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Benutzerdaten"))
|
|
}
|
|
|
|
if req.YearlyHours <= 0 {
|
|
return HandleError(c, ErrInvalidInputMsg("Jahresarbeitsstunden (muss positiv sein)"))
|
|
}
|
|
|
|
if err := UpdateUser(app.DB, userID, req.YearlyHours); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Benutzer"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|
|
|
|
func (app *App) ResetPasswordHandler(c echo.Context) error {
|
|
userID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Benutzer-ID"))
|
|
}
|
|
|
|
var req ResetPasswordRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Passwort-Daten"))
|
|
}
|
|
|
|
if len(req.NewPassword) < 6 {
|
|
return HandleError(c, ErrInvalidInputMsg("Passwort (mindestens 6 Zeichen)"))
|
|
}
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return HandleError(c, ErrInternalMsg(err))
|
|
}
|
|
|
|
if err := ResetUserPassword(app.DB, userID, string(hashedPassword)); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Benutzer"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|
|
|
|
func (app *App) UpdateTimeEntryHandler(c echo.Context) error {
|
|
entryID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Zeiteintrag-ID"))
|
|
}
|
|
|
|
var req UpdateTimeEntryRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Zeiteintrag-Daten"))
|
|
}
|
|
|
|
if req.Date == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Datum"))
|
|
}
|
|
if req.StartTime == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Startzeit"))
|
|
}
|
|
if req.EndTime == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Endzeit"))
|
|
}
|
|
|
|
if err := UpdateTimeEntry(app.DB, entryID, req.Date, req.StartTime, req.EndTime, req.Type); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Zeiteintrag"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
}
|
|
|
|
func (app *App) DeleteTimeEntryHandler(c echo.Context) error {
|
|
entryID, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Zeiteintrag-ID"))
|
|
}
|
|
|
|
if err := DeleteTimeEntry(app.DB, entryID); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Zeiteintrag"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (app *App) GetMyInfoHandler(c echo.Context) error {
|
|
claims, err := getClaims(c)
|
|
if err != nil {
|
|
return HandleError(c, ErrUnauthorizedMsg())
|
|
}
|
|
|
|
user, err := GetUserByID(app.DB, claims.UserID)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Benutzer"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
func (app *App) CreateUserHandler(c echo.Context) error {
|
|
var req CreateUserRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Benutzerdaten"))
|
|
}
|
|
|
|
if req.Username == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Benutzername"))
|
|
}
|
|
if req.Password == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Passwort"))
|
|
}
|
|
if len(req.Password) < 6 {
|
|
return HandleError(c, ErrInvalidInputMsg("Passwort (mindestens 6 Zeichen)"))
|
|
}
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return HandleError(c, ErrInternalMsg(err))
|
|
}
|
|
|
|
if req.YearlyHours == 0 {
|
|
req.YearlyHours = 60.0
|
|
}
|
|
|
|
if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin, req.YearlyHours); err != nil {
|
|
if isDuplicateError(err) {
|
|
return HandleError(c, ErrAlreadyExistsMsg("Benutzername"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusCreated)
|
|
}
|
|
|
|
func (app *App) GetSchoolYearsHandler(c echo.Context) error {
|
|
years, err := GetAllSchoolYears(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if years == nil {
|
|
years = []SchoolYear{}
|
|
}
|
|
return c.JSON(http.StatusOK, years)
|
|
}
|
|
|
|
func (app *App) CreateSchoolYearHandler(c echo.Context) error {
|
|
var req CreateSchoolYearRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Schuljahr-Daten"))
|
|
}
|
|
|
|
if req.Name == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Name"))
|
|
}
|
|
if req.StartDate == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Startdatum"))
|
|
}
|
|
if req.EndDate == "" {
|
|
return HandleError(c, ErrMissingFieldMsg("Enddatum"))
|
|
}
|
|
|
|
if err := CreateSchoolYear(app.DB, req.Name, req.StartDate, req.EndDate); err != nil {
|
|
if isDuplicateError(err) {
|
|
return HandleError(c, ErrAlreadyExistsMsg("Schuljahr"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusCreated)
|
|
}
|
|
|
|
func (app *App) SetActiveSchoolYearHandler(c echo.Context) error {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Schuljahr-ID"))
|
|
}
|
|
|
|
if err := SetActiveSchoolYear(app.DB, id); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Schuljahr"))
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (app *App) GetActiveSchoolYearHandler(c echo.Context) error {
|
|
year, err := GetActiveSchoolYear(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if year == nil {
|
|
return c.JSON(http.StatusOK, map[string]any{"active": false})
|
|
}
|
|
return c.JSON(http.StatusOK, year)
|
|
}
|
|
|
|
func (app *App) GenerateYearlySummaryPDFHandler(c echo.Context) error {
|
|
schoolYear, err := GetActiveSchoolYear(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
if schoolYear == nil {
|
|
return HandleError(c, ErrNoActiveSchoolYearMsg())
|
|
}
|
|
|
|
summary, err := GetYearlyHoursSummary(app.DB)
|
|
if err != nil {
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
pdfBytes, err := GenerateYearlySummaryPDF(schoolYear, summary)
|
|
if err != nil {
|
|
return HandleError(c, ErrInternalMsg(err))
|
|
}
|
|
|
|
filename := fmt.Sprintf("Jahresuebersicht_%s.pdf", schoolYear.Name)
|
|
c.Response().Header().Set("Content-Type", "application/pdf")
|
|
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
|
|
|
return c.Blob(http.StatusOK, "application/pdf", pdfBytes)
|
|
}
|
|
|
|
func (app *App) DeleteSchoolYearHandler(c echo.Context) error {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
return HandleError(c, ErrInvalidInputMsg("Schuljahr-ID"))
|
|
}
|
|
|
|
if err := DeleteSchoolYear(app.DB, id); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return HandleError(c, ErrNotFoundMsg("Schuljahr"))
|
|
}
|
|
if err.Error() == "cannot delete active school year" {
|
|
return HandleError(c, &AppError{
|
|
Code: "CANNOT_DELETE_ACTIVE_SCHOOL_YEAR",
|
|
Message: "Aktives Schuljahr kann nicht gelöscht werden",
|
|
HTTPStatus: http.StatusBadRequest,
|
|
})
|
|
}
|
|
return HandleError(c, ErrDatabaseMsg(err))
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|