feat: added all features for seconde release candidate
This commit is contained in:
parent
9c25956711
commit
e65ba85c43
7 changed files with 1947 additions and 850 deletions
|
|
@ -31,12 +31,12 @@ func InitDB(filepath string) *sql.DB {
|
||||||
func createTables(db *sql.DB) {
|
func createTables(db *sql.DB) {
|
||||||
queries := []string{
|
queries := []string{
|
||||||
`CREATE TABLE IF NOT EXISTS users (
|
`CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
username TEXT UNIQUE NOT NULL,
|
username TEXT UNIQUE NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
is_admin BOOLEAN NOT NULL DEFAULT 0,
|
is_admin BOOLEAN NOT NULL DEFAULT 0,
|
||||||
weekly_hours REAL NOT NULL DEFAULT 40.0,
|
yearly_hours REAL NOT NULL DEFAULT 1800.0, -- 40 Stunden/Woche * 45 Schulwochen
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
)`,
|
)`,
|
||||||
`CREATE TABLE IF NOT EXISTS schedules (
|
`CREATE TABLE IF NOT EXISTS schedules (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
@ -74,10 +74,9 @@ func createTables(db *sql.DB) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin-User anlegen
|
|
||||||
hash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
|
hash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
|
||||||
_, err := db.Exec(`
|
_, err := db.Exec(`
|
||||||
INSERT OR IGNORE INTO users (id, username, password, is_admin, weekly_hours)
|
INSERT OR IGNORE INTO users (id, username, password, is_admin, yearly_hours)
|
||||||
VALUES (?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?)`,
|
||||||
1, "admin", string(hash), true, 40.0,
|
1, "admin", string(hash), true, 40.0,
|
||||||
)
|
)
|
||||||
|
|
@ -104,8 +103,8 @@ func createIndexes(db *sql.DB) {
|
||||||
|
|
||||||
func GetUserByUsername(db *sql.DB, username string) (*User, error) {
|
func GetUserByUsername(db *sql.DB, username string) (*User, error) {
|
||||||
user := &User{}
|
user := &User{}
|
||||||
err := db.QueryRow("SELECT id, username, password, is_admin, weekly_hours FROM users WHERE username = ?", username).
|
err := db.QueryRow("SELECT id, username, password, is_admin, yearly_hours FROM users WHERE username = ?", username).
|
||||||
Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin, &user.WeeklyHours)
|
Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin, &user.YearlyHours)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -114,22 +113,22 @@ func GetUserByUsername(db *sql.DB, username string) (*User, error) {
|
||||||
|
|
||||||
func GetUserByID(db *sql.DB, userID int) (*User, error) {
|
func GetUserByID(db *sql.DB, userID int) (*User, error) {
|
||||||
user := &User{}
|
user := &User{}
|
||||||
err := db.QueryRow("SELECT id, username, password, is_admin, weekly_hours FROM users WHERE id = ?", userID).
|
err := db.QueryRow("SELECT id, username, password, is_admin, yearly_hours FROM users WHERE id = ?", userID).
|
||||||
Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin, &user.WeeklyHours)
|
Scan(&user.ID, &user.Username, &user.Password, &user.IsAdmin, &user.YearlyHours)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateUser(db *sql.DB, username, hashedPassword string, isAdmin bool, weeklyHours float64) error {
|
func CreateUser(db *sql.DB, username, hashedPassword string, isAdmin bool, yearlyHours float64) error {
|
||||||
_, err := db.Exec("INSERT INTO users (username, password, is_admin, weekly_hours) VALUES (?, ?, ?, ?)",
|
_, err := db.Exec("INSERT INTO users (username, password, is_admin, yearly_hours) VALUES (?, ?, ?, ?)",
|
||||||
username, hashedPassword, isAdmin, weeklyHours)
|
username, hashedPassword, isAdmin, yearlyHours)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllUsers(db *sql.DB) ([]User, error) {
|
func GetAllUsers(db *sql.DB) ([]User, error) {
|
||||||
rows, err := db.Query("SELECT id, username, is_admin, weekly_hours FROM users ORDER BY username")
|
rows, err := db.Query("SELECT id, username, is_admin, yearly_hours FROM users ORDER BY username")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +137,7 @@ func GetAllUsers(db *sql.DB) ([]User, error) {
|
||||||
var users []User
|
var users []User
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var u User
|
var u User
|
||||||
if err := rows.Scan(&u.ID, &u.Username, &u.IsAdmin, &u.WeeklyHours); err != nil {
|
if err := rows.Scan(&u.ID, &u.Username, &u.IsAdmin, &u.YearlyHours); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
users = append(users, u)
|
users = append(users, u)
|
||||||
|
|
@ -146,9 +145,9 @@ func GetAllUsers(db *sql.DB) ([]User, error) {
|
||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateUser(db *sql.DB, userID int, weeklyHours float64) error {
|
func UpdateUser(db *sql.DB, userID int, yearlyHours float64) error {
|
||||||
_, err := db.Exec("UPDATE users SET weekly_hours = ? WHERE id = ?",
|
_, err := db.Exec("UPDATE users SET yearly_hours = ? WHERE id = ?",
|
||||||
weeklyHours, userID)
|
yearlyHours, userID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,10 +212,10 @@ func CreateTimeEntry(db *sql.DB, entry *TimeEntry) error {
|
||||||
|
|
||||||
func GetTimeEntriesByUser(db *sql.DB, userID int) ([]TimeEntry, error) {
|
func GetTimeEntriesByUser(db *sql.DB, userID int) ([]TimeEntry, error) {
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT te.id, te.user_id, te.schedule_id, te.date, te.type, te.start_time, te.end_time, te.created_at, u.username
|
SELECT te.id, te.user_id, te.schedule_id, te.date, te.type, te.start_time, te.end_time, te.created_at, u.username
|
||||||
FROM time_entries te
|
FROM time_entries te
|
||||||
JOIN users u ON te.user_id = u.id
|
JOIN users u ON te.user_id = u.id
|
||||||
WHERE te.user_id = ?
|
WHERE te.user_id = ?
|
||||||
ORDER BY te.date DESC, te.created_at DESC
|
ORDER BY te.date DESC, te.created_at DESC
|
||||||
`, userID)
|
`, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -237,7 +236,7 @@ func GetTimeEntriesByUser(db *sql.DB, userID int) ([]TimeEntry, error) {
|
||||||
|
|
||||||
func GetAllTimeEntries(db *sql.DB) ([]TimeEntry, error) {
|
func GetAllTimeEntries(db *sql.DB) ([]TimeEntry, error) {
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT te.id, te.user_id, te.schedule_id, te.date, te.type, te.start_time, te.end_time, te.created_at, u.username
|
SELECT te.id, te.user_id, te.schedule_id, te.date, te.type, te.start_time, te.end_time, te.created_at, u.username
|
||||||
FROM time_entries te
|
FROM time_entries te
|
||||||
JOIN users u ON te.user_id = u.id
|
JOIN users u ON te.user_id = u.id
|
||||||
ORDER BY te.date DESC, te.created_at DESC
|
ORDER BY te.date DESC, te.created_at DESC
|
||||||
|
|
@ -260,34 +259,37 @@ func GetAllTimeEntries(db *sql.DB) ([]TimeEntry, error) {
|
||||||
|
|
||||||
func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT
|
SELECT
|
||||||
te.user_id,
|
te.user_id,
|
||||||
u.username,
|
u.username,
|
||||||
te.date,
|
te.date,
|
||||||
te.start_time,
|
te.start_time,
|
||||||
te.end_time,
|
te.end_time,
|
||||||
te.type,
|
te.type,
|
||||||
u.weekly_hours
|
u.yearly_hours
|
||||||
FROM time_entries te
|
FROM time_entries te
|
||||||
JOIN users u ON te.user_id = u.id
|
JOIN users u ON te.user_id = u.id
|
||||||
ORDER BY te.date DESC
|
ORDER BY te.date DESC
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
hoursMap := make(map[string]*WeeklyHours)
|
hoursMap := make(map[string]*WeeklyHours)
|
||||||
|
userYearlyHours := make(map[int]float64)
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var userID int
|
var userID int
|
||||||
var username, dateStr, startTime, endTime, entryType string
|
var username, dateStr, startTime, endTime, entryType string
|
||||||
var expectedWeeklyHours float64
|
var yearlyHours float64
|
||||||
|
|
||||||
if err := rows.Scan(&userID, &username, &dateStr, &startTime, &endTime, &entryType, &expectedWeeklyHours); err != nil {
|
if err := rows.Scan(&userID, &username, &dateStr, &startTime, &endTime, &entryType, &yearlyHours); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userYearlyHours[userID] = yearlyHours
|
||||||
|
|
||||||
t, err := time.Parse("2006-01-02", dateStr)
|
t, err := time.Parse("2006-01-02", dateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|
@ -303,24 +305,30 @@ func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("%d_%d_%d", userID, year, week)
|
key := fmt.Sprintf("%d_%d_%d", userID, year, week)
|
||||||
|
|
||||||
if existing, exists := hoursMap[key]; exists {
|
if existing, exists := hoursMap[key]; exists {
|
||||||
existing.TotalHours += hours
|
existing.TotalHours += hours
|
||||||
} else {
|
} else {
|
||||||
hoursMap[key] = &WeeklyHours{
|
hoursMap[key] = &WeeklyHours{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Username: username,
|
Username: username,
|
||||||
Year: year,
|
Year: year,
|
||||||
Week: week,
|
Week: week,
|
||||||
TotalHours: hours,
|
TotalHours: hours,
|
||||||
ExpectedHours: expectedWeeklyHours,
|
|
||||||
RemainingHours: expectedWeeklyHours - hours,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yearlyTotals := make(map[int]float64)
|
||||||
for _, h := range hoursMap {
|
for _, h := range hoursMap {
|
||||||
h.RemainingHours = h.ExpectedHours - h.TotalHours
|
yearlyTotals[h.UserID] += h.TotalHours
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range hoursMap {
|
||||||
|
h.YearlyTarget = userYearlyHours[h.UserID]
|
||||||
|
h.YearlyActual = yearlyTotals[h.UserID]
|
||||||
|
|
||||||
|
h.WeeklyTarget = h.YearlyTarget / 45.0
|
||||||
|
h.RemainingYearly = h.YearlyTarget - h.YearlyActual
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []WeeklyHours
|
var result []WeeklyHours
|
||||||
|
|
@ -341,6 +349,57 @@ func GetWeeklyHours(db *sql.DB) ([]WeeklyHours, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetYearlyHoursSummary(db *sql.DB) ([]WeeklyHours, error) {
|
||||||
|
users, err := GetAllUsers(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := GetAllTimeEntries(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userTotals := make(map[int]float64)
|
||||||
|
usernames := make(map[int]string)
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
var hours float64
|
||||||
|
if entry.Type == "lesson" {
|
||||||
|
hours = 1.0
|
||||||
|
} else {
|
||||||
|
hours = calculateHoursDiff(entry.StartTime, entry.EndTime)
|
||||||
|
}
|
||||||
|
userTotals[entry.UserID] += hours
|
||||||
|
usernames[entry.UserID] = entry.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []WeeklyHours
|
||||||
|
for _, user := range users {
|
||||||
|
if !user.IsAdmin {
|
||||||
|
total := userTotals[user.ID]
|
||||||
|
remaining := user.YearlyHours - total
|
||||||
|
|
||||||
|
result = append(result, WeeklyHours{
|
||||||
|
UserID: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
Year: time.Now().Year(),
|
||||||
|
Week: 0,
|
||||||
|
TotalHours: total,
|
||||||
|
YearlyTarget: user.YearlyHours,
|
||||||
|
YearlyActual: total,
|
||||||
|
RemainingYearly: remaining,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
return result[i].Username < result[j].Username
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func calculateHoursDiff(startTime, endTime string) float64 {
|
func calculateHoursDiff(startTime, endTime string) float64 {
|
||||||
parseTime := func(timeStr string) float64 {
|
parseTime := func(timeStr string) float64 {
|
||||||
parts := strings.Split(timeStr, ":")
|
parts := strings.Split(timeStr, ":")
|
||||||
|
|
@ -376,8 +435,8 @@ func DeleteTimeEntriesByUserAndWeek(db *sql.DB, userID int, year int, week int)
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `
|
query := `
|
||||||
DELETE FROM time_entries
|
DELETE FROM time_entries
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
AND date IN (?, ?, ?, ?, ?)
|
AND date IN (?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
_, err := db.Exec(query, userID, dateList[0], dateList[1], dateList[2], dateList[3], dateList[4])
|
_, err := db.Exec(query, userID, dateList[0], dateList[1], dateList[2], dateList[3], dateList[4])
|
||||||
|
|
@ -393,9 +452,9 @@ func CheckUserHasEntriesForWeek(db *sql.DB, userID int, year int, week int) (boo
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `
|
query := `
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM time_entries
|
FROM time_entries
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
AND date IN (?, ?, ?, ?, ?)
|
AND date IN (?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ go 1.25.3
|
||||||
require (
|
require (
|
||||||
github.com/labstack/echo/v4 v4.13.4
|
github.com/labstack/echo/v4 v4.13.4
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
|
golang.org/x/time v0.11.0
|
||||||
modernc.org/sqlite v1.40.0
|
modernc.org/sqlite v1.40.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,7 +23,6 @@ require (
|
||||||
golang.org/x/net v0.45.0 // indirect
|
golang.org/x/net v0.45.0 // indirect
|
||||||
golang.org/x/sys v0.37.0 // indirect
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
golang.org/x/text v0.30.0 // indirect
|
golang.org/x/text v0.30.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
|
||||||
modernc.org/libc v1.66.10 // indirect
|
modernc.org/libc v1.66.10 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ type App struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login Handler
|
|
||||||
func (app *App) LoginHandler(c echo.Context) error {
|
func (app *App) LoginHandler(c echo.Context) error {
|
||||||
var req LoginRequest
|
var req LoginRequest
|
||||||
if err := c.Bind(&req); err != nil {
|
if err := c.Bind(&req); err != nil {
|
||||||
|
|
@ -44,7 +43,6 @@ func (app *App) LoginHandler(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, response)
|
return c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule Handlers
|
|
||||||
func (app *App) GetSchedulesHandler(c echo.Context) error {
|
func (app *App) GetSchedulesHandler(c echo.Context) error {
|
||||||
schedules, err := GetAllSchedules(app.DB)
|
schedules, err := GetAllSchedules(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -76,33 +74,62 @@ func (app *App) DeleteScheduleHandler(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// // User Handlers
|
func (app *App) GetYearlyHoursSummaryHandler(c echo.Context) error {
|
||||||
// func (app *App) CreateUserHandler(c echo.Context) error {
|
hours, err := GetYearlyHoursSummary(app.DB)
|
||||||
// var req CreateUserRequest
|
if err != nil {
|
||||||
// if err := c.Bind(&req); err != nil {
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
// return echo.NewHTTPError(http.StatusBadRequest, "invalid request")
|
}
|
||||||
// }
|
if hours == nil {
|
||||||
|
hours = []WeeklyHours{}
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, hours)
|
||||||
|
}
|
||||||
|
|
||||||
// hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
func (app *App) AdminCreateTimeEntryHandler(c echo.Context) error {
|
||||||
// if err != nil {
|
isAdmin, _ := c.Get("is_admin").(bool)
|
||||||
// return echo.NewHTTPError(http.StatusInternalServerError, "error hashing password")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin); err != nil {
|
if !isAdmin {
|
||||||
// return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusForbidden, "Only admins can create entries for others")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return c.JSON(http.StatusCreated, map[string]string{"message": "user created"})
|
var req struct {
|
||||||
// }
|
UserID int `json:"user_id"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
EndTime string `json:"end_time"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Bind(&req); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid request")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := TimeEntry{
|
||||||
|
UserID: req.UserID,
|
||||||
|
Date: req.Date,
|
||||||
|
StartTime: req.StartTime,
|
||||||
|
EndTime: req.EndTime,
|
||||||
|
Type: req.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
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) GetUsersHandler(c echo.Context) error {
|
func (app *App) GetUsersHandler(c echo.Context) error {
|
||||||
users, err := GetAllUsers(app.DB)
|
users, err := GetAllUsers(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
if users == nil {
|
||||||
|
users = []User{}
|
||||||
|
}
|
||||||
return c.JSON(http.StatusOK, users)
|
return c.JSON(http.StatusOK, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,10 +143,9 @@ func (app *App) DeleteUserHandler(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time Entry Handlers
|
|
||||||
func (app *App) CreateTimeEntryHandler(c echo.Context) error {
|
func (app *App) CreateTimeEntryHandler(c echo.Context) error {
|
||||||
userID := c.Get("user_id").(int)
|
userID := c.Get("user_id").(int)
|
||||||
|
|
||||||
|
|
@ -144,6 +170,9 @@ func (app *App) GetMyTimeEntriesHandler(c echo.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
if entries == nil {
|
||||||
|
entries = []TimeEntry{}
|
||||||
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, entries)
|
return c.JSON(http.StatusOK, entries)
|
||||||
}
|
}
|
||||||
|
|
@ -189,6 +218,9 @@ func (app *App) GetAllTimeEntriesHandler(c echo.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
if entries == nil {
|
||||||
|
entries = []TimeEntry{}
|
||||||
|
}
|
||||||
return c.JSON(http.StatusOK, entries)
|
return c.JSON(http.StatusOK, entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,6 +229,9 @@ func (app *App) GetWeeklyHoursHandler(c echo.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
if hours == nil {
|
||||||
|
hours = []WeeklyHours{}
|
||||||
|
}
|
||||||
return c.JSON(http.StatusOK, hours)
|
return c.JSON(http.StatusOK, hours)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +252,7 @@ func (app *App) DeleteWeekEntries(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeekDates struct {
|
type WeekDates struct {
|
||||||
|
|
@ -320,7 +355,7 @@ func (app *App) UpdateUserHandler(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := UpdateUser(app.DB, userID, req.WeeklyHours); err != nil {
|
if err := UpdateUser(app.DB, userID, req.YearlyHours); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,47 +413,18 @@ func (app *App) DeleteTimeEntryHandler(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetMyWeeklySummaryHandler(c echo.Context) error {
|
func (app *App) GetMyInfoHandler(c echo.Context) error {
|
||||||
userID := c.Get("user_id").(int)
|
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)
|
user, err := GetUserByID(app.DB, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
allHours, err := GetWeeklyHours(app.DB)
|
return c.JSON(http.StatusOK, user)
|
||||||
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 {
|
func (app *App) CreateUserHandler(c echo.Context) error {
|
||||||
|
|
@ -432,11 +438,11 @@ func (app *App) CreateUserHandler(c echo.Context) error {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Error hashing password")
|
return echo.NewHTTPError(http.StatusInternalServerError, "Error hashing password")
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.WeeklyHours == 0 {
|
if req.YearlyHours == 0 {
|
||||||
req.WeeklyHours = 40.0
|
req.YearlyHours = 1800.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin, req.WeeklyHours); err != nil {
|
if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin, req.YearlyHours); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ func main() {
|
||||||
protected.DELETE("/my-time-entries/week", app.DeleteWeekEntries)
|
protected.DELETE("/my-time-entries/week", app.DeleteWeekEntries)
|
||||||
protected.GET("/week-dates", app.GetWeekDates)
|
protected.GET("/week-dates", app.GetWeekDates)
|
||||||
protected.GET("/week-has-entries", app.CheckWeekHasEntries)
|
protected.GET("/week-has-entries", app.CheckWeekHasEntries)
|
||||||
protected.GET("/my-weekly-summary", app.GetMyWeeklySummaryHandler)
|
protected.GET("/yearly-hours-summary", app.GetYearlyHoursSummaryHandler)
|
||||||
|
protected.GET("/my-info", app.GetMyInfoHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
admin := e.Group("/api/admin")
|
admin := e.Group("/api/admin")
|
||||||
|
|
@ -59,9 +60,10 @@ func main() {
|
||||||
admin.GET("/time-entries", app.GetAllTimeEntriesHandler)
|
admin.GET("/time-entries", app.GetAllTimeEntriesHandler)
|
||||||
admin.GET("/weekly-hours", app.GetWeeklyHoursHandler)
|
admin.GET("/weekly-hours", app.GetWeeklyHoursHandler)
|
||||||
admin.PUT("/users/:id", app.UpdateUserHandler)
|
admin.PUT("/users/:id", app.UpdateUserHandler)
|
||||||
admin.POST("/users/:id/reset-password", app.ResetPasswordHandler)
|
admin.PUT("/users/:id/reset-password", app.ResetPasswordHandler)
|
||||||
admin.PUT("/time-entries/:id", app.UpdateTimeEntryHandler)
|
admin.PUT("/time-entries/:id", app.UpdateTimeEntryHandler)
|
||||||
admin.DELETE("/time-entries/:id", app.DeleteTimeEntryHandler)
|
admin.DELETE("/time-entries/:id", app.DeleteTimeEntryHandler)
|
||||||
|
admin.POST("/time-entry", app.AdminCreateTimeEntryHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Static("/", "./static")
|
e.Static("/", "./static")
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,15 @@ type TimeEntry struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeeklyHours struct {
|
type WeeklyHours struct {
|
||||||
UserID int `json:"user_id"`
|
UserID int `json:"user_id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Week int `json:"week"`
|
Week int `json:"week"`
|
||||||
Year int `json:"year"`
|
Year int `json:"year"`
|
||||||
TotalHours float64 `json:"total_hours"`
|
TotalHours float64 `json:"total_hours"`
|
||||||
ExpectedHours float64 `json:"expected_hours"`
|
YearlyTarget float64 `json:"yearly_target"` // NEU
|
||||||
RemainingHours float64 `json:"remaining_hours"`
|
YearlyActual float64 `json:"yearly_actual"` // NEU
|
||||||
|
WeeklyTarget float64 `json:"weekly_target"` // NEU
|
||||||
|
RemainingYearly float64 `json:"remaining_yearly"` // NEU
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
|
@ -29,7 +31,7 @@ type User struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"-"`
|
Password string `json:"-"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
WeeklyHours float64 `json:"weekly_hours"`
|
YearlyHours float64 `json:"yearly_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Schedule struct {
|
type Schedule struct {
|
||||||
|
|
@ -56,12 +58,12 @@ type CreateUserRequest struct {
|
||||||
Username string `json:"username" validate:"required"`
|
Username string `json:"username" validate:"required"`
|
||||||
Password string `json:"password" validate:"required,min=6"`
|
Password string `json:"password" validate:"required,min=6"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
WeeklyHours float64 `json:"weekly_hours"`
|
YearlyHours float64 `json:"yearly_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserRequest struct {
|
type UpdateUserRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
WeeklyHours float64 `json:"weekly_hours"`
|
YearlyHours float64 `json:"yearly_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResetPasswordRequest struct {
|
type ResetPasswordRequest struct {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,3 @@
|
||||||
# version: '3.8'
|
|
||||||
|
|
||||||
# services:
|
|
||||||
# timetracking:
|
|
||||||
# build: .
|
|
||||||
# container_name: school-timetracking
|
|
||||||
# ports:
|
|
||||||
# - "8080:8080"
|
|
||||||
# volumes:
|
|
||||||
# - ./data:/data
|
|
||||||
# environment:
|
|
||||||
# - PORT=8080
|
|
||||||
# - DB_PATH=/data/timetracking.db
|
|
||||||
# restart: unless-stopped
|
|
||||||
services:
|
services:
|
||||||
timetracking:
|
timetracking:
|
||||||
build: .
|
build: .
|
||||||
|
|
@ -21,6 +7,8 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- PORT=8080
|
- PORT=8080
|
||||||
- DB_PATH=/data/timetracking.db
|
- DB_PATH=/data/timetracking.db
|
||||||
|
- JWT_SECRET=your-default-secret-change-me
|
||||||
|
- TZ=Europe/Berlin # Optional: Zeitzone
|
||||||
volumes:
|
volumes:
|
||||||
- timetracking-data:/data
|
- timetracking-data:/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
@ -34,4 +22,3 @@ volumes:
|
||||||
networks:
|
networks:
|
||||||
timetracking-net:
|
timetracking-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue