feat: change from Standart Library to echo framework to simplify development
This commit is contained in:
parent
d74046522b
commit
4514ce44a2
6 changed files with 254 additions and 275 deletions
|
|
@ -3,6 +3,7 @@ module school-timetracker
|
||||||
go 1.25.3
|
go 1.25.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/labstack/echo/v4 v4.13.4
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
modernc.org/sqlite v1.40.0
|
modernc.org/sqlite v1.40.0
|
||||||
)
|
)
|
||||||
|
|
@ -10,11 +11,18 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // 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/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
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,52 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
||||||
|
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
||||||
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
|
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||||
|
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -14,28 +13,25 @@ type App struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
// Login Handler
|
||||||
|
func (app *App) LoginHandler(c echo.Context) error {
|
||||||
var req LoginRequest
|
var req LoginRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := c.Bind(&req); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid request")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := GetUserByUsername(app.DB, req.Username)
|
user, err := GetUserByUsername(app.DB, req.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
return echo.NewHTTPError(http.StatusUnauthorized, "invalid credentials")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
||||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
return echo.NewHTTPError(http.StatusUnauthorized, "invalid credentials")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := createToken(user.ID, user.Username, user.IsAdmin)
|
token, err := createToken(user.ID, user.Username, user.IsAdmin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error creating token", http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, "error creating token")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response := LoginResponse{
|
response := LoginResponse{
|
||||||
|
|
@ -44,179 +40,125 @@ func (app *App) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
IsAdmin: user.IsAdmin,
|
IsAdmin: user.IsAdmin,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
return c.JSON(http.StatusOK, response)
|
||||||
json.NewEncoder(w).Encode(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetSchedulesHandler(w http.ResponseWriter, r *http.Request) {
|
// Schedule Handlers
|
||||||
|
func (app *App) GetSchedulesHandler(c echo.Context) error {
|
||||||
schedules, err := GetAllSchedules(app.DB)
|
schedules, err := GetAllSchedules(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
}
|
||||||
|
return c.JSON(http.StatusOK, schedules)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
func (app *App) CreateScheduleHandler(c echo.Context) error {
|
||||||
json.NewEncoder(w).Encode(schedules)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *App) CreateScheduleHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var schedule Schedule
|
var schedule Schedule
|
||||||
if err := json.NewDecoder(r.Body).Decode(&schedule); err != nil {
|
if err := c.Bind(&schedule); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid request")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := CreateSchedule(app.DB, &schedule); err != nil {
|
if err := CreateSchedule(app.DB, &schedule); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
return c.JSON(http.StatusCreated, map[string]string{"message": "schedule created"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) DeleteScheduleHandler(w http.ResponseWriter, r *http.Request) {
|
func (app *App) DeleteScheduleHandler(c echo.Context) error {
|
||||||
idStr := r.URL.Query().Get("id")
|
id, err := strconv.Atoi(c.QueryParam("id"))
|
||||||
id, err := strconv.Atoi(idStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid id")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := DeleteSchedule(app.DB, id); err != nil {
|
if err := DeleteSchedule(app.DB, id); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
return c.NoContent(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
// User Handlers
|
||||||
var req struct {
|
func (app *App) CreateUserHandler(c echo.Context) error {
|
||||||
Username string `json:"username"`
|
var req CreateUserRequest
|
||||||
Password string `json:"password"`
|
if err := c.Bind(&req); err != nil {
|
||||||
IsAdmin bool `json:"is_admin"`
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid request")
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error hashing password", http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, "error hashing password")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin); err != nil {
|
if err := CreateUser(app.DB, req.Username, string(hashedPassword), req.IsAdmin); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
return c.JSON(http.StatusCreated, map[string]string{"message": "user created"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetUsersHandler(w http.ResponseWriter, r *http.Request) {
|
func (app *App) GetUsersHandler(c echo.Context) error {
|
||||||
users, err := GetAllUsers(app.DB)
|
users, err := GetAllUsers(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
}
|
||||||
|
return c.JSON(http.StatusOK, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
func (app *App) DeleteUserHandler(c echo.Context) error {
|
||||||
json.NewEncoder(w).Encode(users)
|
id, err := strconv.Atoi(c.QueryParam("id"))
|
||||||
}
|
|
||||||
|
|
||||||
// func (app *App) CreateTimeEntryHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// userIDStr := r.Header.Get("X-User-ID")
|
|
||||||
// userID, _ := strconv.Atoi(userIDStr)
|
|
||||||
|
|
||||||
// var entry TimeEntry
|
|
||||||
// if err := json.NewDecoder(r.Body).Decode(&entry); err != nil {
|
|
||||||
// http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// entry.UserID = userID
|
|
||||||
|
|
||||||
// if err := CreateTimeEntry(app.DB, &entry); err != nil {
|
|
||||||
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// w.WriteHeader(http.StatusCreated)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (app *App) GetMyTimeEntriesHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userIDStr := r.Header.Get("X-User-ID")
|
|
||||||
userID, _ := strconv.Atoi(userIDStr)
|
|
||||||
|
|
||||||
entries, err := GetTimeEntriesByUser(app.DB, userID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid id")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
if err := DeleteUser(app.DB, id); err != nil {
|
||||||
json.NewEncoder(w).Encode(entries)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) GetAllTimeEntriesHandler(w http.ResponseWriter, r *http.Request) {
|
return c.NoContent(http.StatusOK)
|
||||||
entries, err := GetAllTimeEntries(app.DB)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
// Time Entry Handlers
|
||||||
json.NewEncoder(w).Encode(entries)
|
func (app *App) CreateTimeEntryHandler(c echo.Context) error {
|
||||||
}
|
userID := c.Get("user_id").(int)
|
||||||
|
|
||||||
func (app *App) CreateTimeEntryHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userIDStr := r.Header.Get("X-User-ID")
|
|
||||||
userID, _ := strconv.Atoi(userIDStr)
|
|
||||||
|
|
||||||
var entry TimeEntry
|
var entry TimeEntry
|
||||||
if err := json.NewDecoder(r.Body).Decode(&entry); err != nil {
|
if err := c.Bind(&entry); err != nil {
|
||||||
log.Print("Error on Decoding occured")
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid request")
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.UserID = userID
|
entry.UserID = userID
|
||||||
|
|
||||||
if err := CreateTimeEntry(app.DB, &entry); err != nil {
|
if err := CreateTimeEntry(app.DB, &entry); err != nil {
|
||||||
log.Print("Error on creating time entry in Database occured")
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
return c.JSON(http.StatusCreated, map[string]string{"message": "time entry created"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
func (app *App) GetMyTimeEntriesHandler(c echo.Context) error {
|
||||||
idStr := r.URL.Query().Get("id")
|
userID := c.Get("user_id").(int)
|
||||||
id, err := strconv.Atoi(idStr)
|
|
||||||
|
entries, err := GetTimeEntriesByUser(app.DB, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := DeleteUser(app.DB, id); err != nil {
|
return c.JSON(http.StatusOK, entries)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
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(w http.ResponseWriter, r *http.Request) {
|
func (app *App) GetWeeklyHoursHandler(c echo.Context) error {
|
||||||
hours, err := GetWeeklyHours(app.DB)
|
hours, err := GetWeeklyHours(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return c.JSON(http.StatusOK, hours)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(hours)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,13 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Database Setup
|
||||||
dbPath := os.Getenv("DB_PATH")
|
dbPath := os.Getenv("DB_PATH")
|
||||||
if dbPath == "" {
|
if dbPath == "" {
|
||||||
dbPath = "./timetracking.db"
|
dbPath = "./timetracking.db"
|
||||||
|
|
@ -17,32 +21,78 @@ func main() {
|
||||||
|
|
||||||
app := &App{DB: db}
|
app := &App{DB: db}
|
||||||
|
|
||||||
|
// Echo instance
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
e.Use(middleware.Logger())
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||||
|
AllowOrigins: []string{"*"},
|
||||||
|
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
|
||||||
|
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Custom error handler
|
||||||
|
e.HTTPErrorHandler = customHTTPErrorHandler
|
||||||
|
|
||||||
// Public routes
|
// Public routes
|
||||||
http.HandleFunc("/api/login", CORS(app.LoginHandler))
|
e.POST("/api/login", app.LoginHandler)
|
||||||
|
|
||||||
// Protected routes
|
// Protected routes group
|
||||||
http.HandleFunc("/api/schedules", CORS(AuthMiddleware(app.GetSchedulesHandler)))
|
protected := e.Group("/api")
|
||||||
http.HandleFunc("/api/time-entries", CORS(AuthMiddleware(app.CreateTimeEntryHandler)))
|
protected.Use(JWTMiddleware())
|
||||||
http.HandleFunc("/api/my-time-entries", CORS(AuthMiddleware(app.GetMyTimeEntriesHandler)))
|
{
|
||||||
|
protected.GET("/schedules", app.GetSchedulesHandler)
|
||||||
|
protected.POST("/time-entries", app.CreateTimeEntryHandler)
|
||||||
|
protected.GET("/my-time-entries", app.GetMyTimeEntriesHandler)
|
||||||
|
}
|
||||||
|
|
||||||
// Admin routes
|
// Admin routes group
|
||||||
http.HandleFunc("/api/admin/schedules", CORS(AdminMiddleware(app.CreateScheduleHandler)))
|
admin := e.Group("/api/admin")
|
||||||
http.HandleFunc("/api/admin/schedules/delete", CORS(AdminMiddleware(app.DeleteScheduleHandler)))
|
admin.Use(JWTMiddleware())
|
||||||
http.HandleFunc("/api/admin/users", CORS(AdminMiddleware(app.CreateUserHandler)))
|
admin.Use(AdminMiddleware())
|
||||||
http.HandleFunc("/api/admin/users/list", CORS(AdminMiddleware(app.GetUsersHandler)))
|
{
|
||||||
http.HandleFunc("/api/admin/users/delete", CORS(AdminMiddleware(app.DeleteUserHandler))) // Neu
|
admin.POST("/schedules", app.CreateScheduleHandler)
|
||||||
http.HandleFunc("/api/admin/time-entries", CORS(AdminMiddleware(app.GetAllTimeEntriesHandler)))
|
admin.DELETE("/schedules/delete", app.DeleteScheduleHandler)
|
||||||
http.HandleFunc("/api/admin/weekly-hours", CORS(AdminMiddleware(app.GetWeeklyHoursHandler))) // Neu
|
admin.POST("/users", app.CreateUserHandler)
|
||||||
|
admin.GET("/users/list", app.GetUsersHandler)
|
||||||
|
admin.DELETE("/users/delete", app.DeleteUserHandler)
|
||||||
|
admin.GET("/time-entries", app.GetAllTimeEntriesHandler)
|
||||||
|
admin.GET("/weekly-hours", app.GetWeeklyHoursHandler)
|
||||||
|
}
|
||||||
|
|
||||||
// Serve frontend
|
// Static files
|
||||||
fs := http.FileServer(http.Dir("./static"))
|
e.Static("/", "./static")
|
||||||
http.Handle("/", fs)
|
|
||||||
|
|
||||||
|
// Start server
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
port = "8080"
|
port = "8080"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Server starting on port %s", port)
|
log.Printf("Server starting on port %s", port)
|
||||||
log.Fatal(http.ListenAndServe(":"+port, nil))
|
e.Logger.Fatal(e.Start(":" + port))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom error handler for better error responses
|
||||||
|
func customHTTPErrorHandler(err error, c echo.Context) {
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
message := "Internal Server Error"
|
||||||
|
|
||||||
|
if he, ok := err.(*echo.HTTPError); ok {
|
||||||
|
code = he.Code
|
||||||
|
message = he.Message.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't override response if already written
|
||||||
|
if !c.Response().Committed {
|
||||||
|
if c.Request().Method == http.MethodHead {
|
||||||
|
c.NoContent(code)
|
||||||
|
} else {
|
||||||
|
c.JSON(code, map[string]string{
|
||||||
|
"error": message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,64 +6,35 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
var jwtSecret = []byte("your-secret-key-change-in-production")
|
var jwtSecret = []byte("your-secret-key-change-in-production")
|
||||||
|
|
||||||
type Claims struct {
|
// JWT Token Funktionen (bleiben gleich)
|
||||||
UserID int `json:"user_id"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
IsAdmin bool `json:"is_admin"`
|
|
||||||
Exp int64 `json:"exp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type responseWriter struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
status int
|
|
||||||
wroteHeader bool
|
|
||||||
written int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapResponseWriter(w http.ResponseWriter) *responseWriter {
|
|
||||||
return &responseWriter{
|
|
||||||
ResponseWriter: w,
|
|
||||||
status: http.StatusOK,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) WriteHeader(code int) {
|
|
||||||
if rw.wroteHeader {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rw.status = code
|
|
||||||
rw.ResponseWriter.WriteHeader(code)
|
|
||||||
rw.wroteHeader = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw *responseWriter) Write(b []byte) (int, error) {
|
|
||||||
if !rw.wroteHeader {
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
n, err := rw.ResponseWriter.Write(b)
|
|
||||||
rw.written += int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func createToken(userID int, username string, isAdmin bool) (string, error) {
|
func createToken(userID int, username string, isAdmin bool) (string, error) {
|
||||||
claims := Claims{
|
claims := Claims{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Username: username,
|
Username: username,
|
||||||
IsAdmin: isAdmin,
|
IsAdmin: isAdmin,
|
||||||
Exp: time.Now().Add(24 * time.Hour).Unix(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
|
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`))
|
||||||
|
|
||||||
payload, _ := json.Marshal(claims)
|
// Füge Expiration hinzu
|
||||||
|
claimsWithExp := map[string]interface{}{
|
||||||
|
"user_id": claims.UserID,
|
||||||
|
"username": claims.Username,
|
||||||
|
"is_admin": claims.IsAdmin,
|
||||||
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, _ := json.Marshal(claimsWithExp)
|
||||||
payloadEncoded := base64.RawURLEncoding.EncodeToString(payload)
|
payloadEncoded := base64.RawURLEncoding.EncodeToString(payload)
|
||||||
|
|
||||||
message := header + "." + payloadEncoded
|
message := header + "." + payloadEncoded
|
||||||
|
|
@ -95,86 +66,68 @@ func verifyToken(tokenString string) (*Claims, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var claims Claims
|
var claimsMap map[string]interface{}
|
||||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
if err := json.Unmarshal(payload, &claimsMap); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().Unix() > claims.Exp {
|
// Check expiration
|
||||||
|
if exp, ok := claimsMap["exp"].(float64); ok {
|
||||||
|
if time.Now().Unix() > int64(exp) {
|
||||||
return nil, fmt.Errorf("token expired")
|
return nil, fmt.Errorf("token expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &claims, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
claims := &Claims{
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
UserID: int(claimsMap["user_id"].(float64)),
|
||||||
authHeader := r.Header.Get("Authorization")
|
Username: claimsMap["username"].(string),
|
||||||
|
IsAdmin: claimsMap["is_admin"].(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo JWT Middleware
|
||||||
|
func JWTMiddleware() echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
authHeader := c.Request().Header.Get("Authorization")
|
||||||
if authHeader == "" {
|
if authHeader == "" {
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
return echo.NewHTTPError(http.StatusUnauthorized, "missing authorization header")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||||
claims, err := verifyToken(tokenString)
|
claims, err := verifyToken(tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
return echo.NewHTTPError(http.StatusUnauthorized, "invalid token")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("X-User-ID", fmt.Sprintf("%d", claims.UserID))
|
// Store claims in context
|
||||||
r.Header.Set("X-Username", claims.Username)
|
c.Set("user_id", claims.UserID)
|
||||||
r.Header.Set("X-Is-Admin", fmt.Sprintf("%t", claims.IsAdmin))
|
c.Set("username", claims.Username)
|
||||||
|
c.Set("is_admin", claims.IsAdmin)
|
||||||
|
|
||||||
next(w, r)
|
return next(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AdminMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
// Admin Middleware
|
||||||
return AuthMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
func AdminMiddleware() echo.MiddlewareFunc {
|
||||||
isAdmin := r.Header.Get("X-Is-Admin") == "true"
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
if !isAdmin {
|
return func(c echo.Context) error {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
isAdmin, ok := c.Get("is_admin").(bool)
|
||||||
return
|
if !ok || !isAdmin {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "admin access required")
|
||||||
}
|
}
|
||||||
next(w, r)
|
return next(c)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
func LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
// Custom Logger Middleware (optional - Echo hat bereits einen)
|
||||||
start := time.Now()
|
func CustomLogger() echo.MiddlewareFunc {
|
||||||
|
return middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||||
wrapped := wrapResponseWriter(w)
|
Format: "${time_rfc3339} | ${status} | ${latency_human} | ${method} ${uri}\n",
|
||||||
|
|
||||||
defer func() {
|
|
||||||
slog.Info("http request",
|
|
||||||
"method", r.Method,
|
|
||||||
"path", r.URL.Path,
|
|
||||||
"query", r.URL.RawQuery,
|
|
||||||
"status", wrapped.status,
|
|
||||||
"duration_ms", time.Since(start).Milliseconds(),
|
|
||||||
"client_ip", r.RemoteAddr,
|
|
||||||
"user_agent", r.UserAgent(),
|
|
||||||
"bytes_written", wrapped.written,
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
|
|
||||||
next(wrapped, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CORS(next http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return LoggingMiddleware(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
||||||
|
|
||||||
if r.Method == "OPTIONS" {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next(w, r)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TimeEntry struct {
|
type TimeEntry struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
|
@ -10,10 +8,10 @@ type TimeEntry struct {
|
||||||
ScheduleID int `json:"schedule_id"`
|
ScheduleID int `json:"schedule_id"`
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
StartTime string `json:"start_time"` // Neu
|
StartTime string `json:"start_time"`
|
||||||
EndTime string `json:"end_time"` // Neu
|
EndTime string `json:"end_time"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
Username string `json:"username"` // Neu - für Join
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeeklyHours struct {
|
type WeeklyHours struct {
|
||||||
|
|
@ -33,25 +31,16 @@ type User struct {
|
||||||
|
|
||||||
type Schedule struct {
|
type Schedule struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
DayOfWeek int `json:"day_of_week"` // 0=Monday, 4=Friday
|
DayOfWeek int `json:"day_of_week"`
|
||||||
StartTime string `json:"start_time"`
|
StartTime string `json:"start_time"`
|
||||||
EndTime string `json:"end_time"`
|
EndTime string `json:"end_time"`
|
||||||
Type string `json:"type"` // "lesson" or "break"
|
Type string `json:"type"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// type TimeEntry struct {
|
|
||||||
// ID int `json:"id"`
|
|
||||||
// UserID int `json:"user_id"`
|
|
||||||
// ScheduleID int `json:"schedule_id"`
|
|
||||||
// Date string `json:"date"`
|
|
||||||
// Type string `json:"type"` // "lesson" or "break"
|
|
||||||
// CreatedAt time.Time `json:"created_at"`
|
|
||||||
// }
|
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LoginResponse struct {
|
||||||
|
|
@ -59,3 +48,16 @@ type LoginResponse struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
IsAdmin bool `json:"is_admin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateUserRequest struct {
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Password string `json:"password" validate:"required,min=6"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims für JWT
|
||||||
|
type Claims struct {
|
||||||
|
UserID int `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue