Compare commits
4 Commits
1.1.3
...
a6ea625e8f
| Author | SHA1 | Date | |
|---|---|---|---|
| a6ea625e8f | |||
| 386f11ec7e | |||
| 6c0a8bca64 | |||
| 6e238c4532 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,3 +39,4 @@ node_modules
|
|||||||
atlas.hcl
|
atlas.hcl
|
||||||
.scannerwork
|
.scannerwork
|
||||||
Backend/logs
|
Backend/logs
|
||||||
|
.worktime.txt
|
||||||
|
|||||||
82
Backend/endpoints/auto-kurzarbeit.go
Normal file
82
Backend/endpoints/auto-kurzarbeit.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arbeitszeitmessung/helper"
|
||||||
|
"arbeitszeitmessung/helper/paramParser"
|
||||||
|
"arbeitszeitmessung/models"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func KurzarbeitFillHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
helper.SetCors(w)
|
||||||
|
switch r.Method {
|
||||||
|
case "GET":
|
||||||
|
fillKurzarbeit(r, w)
|
||||||
|
default:
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillKurzarbeit(r *http.Request, w http.ResponseWriter) {
|
||||||
|
bookingTypeKurzarbeit, err := getKurzarbeitBookingType()
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("Error getting BookingType Kurzarbeit %v\n", slog.Any("Error", err))
|
||||||
|
}
|
||||||
|
users, err := models.GetAllUsers()
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("Error getting user list %v\n", slog.Any("Error", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
pp := paramParser.New(r.URL.Query())
|
||||||
|
startDate := pp.ParseTimestampFallback("date", time.DateOnly, time.Now())
|
||||||
|
|
||||||
|
var kurzarbeitAdded int
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
days := models.GetDays(user, startDate, startDate.AddDate(0, 0, 1), false)
|
||||||
|
if len(days) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
day := days[len(days)-1]
|
||||||
|
if !day.IsKurzArbeit() || !day.IsWorkDay() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if day.GetWorktimeReal(user, models.WorktimeBaseDay) >= day.GetWorktimeVirtual(user, models.WorktimeBaseDay) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
worktimeKurzarbeit := day.GetWorktimeVirtual(user, models.WorktimeBaseDay) - day.GetWorktimeReal(user, models.WorktimeBaseDay)
|
||||||
|
|
||||||
|
if wDay, ok := day.(*models.WorkDay); !ok || len(wDay.Bookings) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
workday, _ := day.(*models.WorkDay)
|
||||||
|
|
||||||
|
lastBookingTime := workday.Bookings[len(workday.Bookings)-1].Timestamp
|
||||||
|
kurzarbeitBegin := (*models.Booking).New(nil, user.CardUID, 0, 1, bookingTypeKurzarbeit.Id)
|
||||||
|
kurzarbeitEnd := (*models.Booking).New(nil, user.CardUID, 0, 2, bookingTypeKurzarbeit.Id)
|
||||||
|
kurzarbeitBegin.Timestamp = lastBookingTime.Add(time.Minute)
|
||||||
|
kurzarbeitEnd.Timestamp = lastBookingTime.Add(worktimeKurzarbeit)
|
||||||
|
|
||||||
|
kurzarbeitBegin.InsertWithTimestamp()
|
||||||
|
kurzarbeitEnd.InsertWithTimestamp()
|
||||||
|
kurzarbeitAdded += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(kurzarbeitAdded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKurzarbeitBookingType() (models.BookingType, error) {
|
||||||
|
for _, bookingType := range models.GetBookingTypesCached() {
|
||||||
|
if bookingType.Name == "Kurzarbeit" {
|
||||||
|
return bookingType, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models.BookingType{}, errors.New("No Booking Type found")
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func autoLogout(w http.ResponseWriter) {
|
func autoLogout(w http.ResponseWriter) {
|
||||||
users, err := (*models.User).GetAll(nil)
|
users, err := models.GetAllUsers()
|
||||||
var logged_out_users []models.User
|
var logged_out_users []models.User
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error getting user list %v\n", err)
|
fmt.Printf("Error getting user list %v\n", err)
|
||||||
@@ -20,7 +20,7 @@ func convertDaysToTypst(days []models.IWorkDay, u models.User) ([]typstDay, erro
|
|||||||
var typstDays []typstDay
|
var typstDays []typstDay
|
||||||
for _, day := range days {
|
for _, day := range days {
|
||||||
var thisTypstDay typstDay
|
var thisTypstDay typstDay
|
||||||
work, pause, overtime := day.GetTimesVirtual(u, models.WorktimeBaseWeek)
|
work, pause, overtime := day.GetTimesReal(u, models.WorktimeBaseWeek)
|
||||||
thisTypstDay.Date = day.Date().Format(DE_DATE)
|
thisTypstDay.Date = day.Date().Format(DE_DATE)
|
||||||
thisTypstDay.Worktime = helper.FormatDurationFill(work, true)
|
thisTypstDay.Worktime = helper.FormatDurationFill(work, true)
|
||||||
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
|
thisTypstDay.Pausetime = helper.FormatDurationFill(pause, true)
|
||||||
@@ -45,15 +45,6 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
|
|||||||
typstDayPart.IsWorkDay = true
|
typstDayPart.IsWorkDay = true
|
||||||
typstDayParts = append(typstDayParts, typstDayPart)
|
typstDayParts = append(typstDayParts, typstDayPart)
|
||||||
}
|
}
|
||||||
if day.IsKurzArbeit() {
|
|
||||||
tsFrom, tsTo := workDay.GenerateKurzArbeitBookings(user)
|
|
||||||
typstDayParts = append(typstDayParts, typstDayPart{
|
|
||||||
BookingFrom: tsFrom.Format("15:04"),
|
|
||||||
BookingTo: tsTo.Format("15:04"),
|
|
||||||
WorkType: "Kurzarbeit",
|
|
||||||
IsWorkDay: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
absentDay, _ := day.(*models.Absence)
|
absentDay, _ := day.(*models.Absence)
|
||||||
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
|
typstDayParts = append(typstDayParts, typstDayPart{IsWorkDay: false, WorkType: absentDay.AbwesenheitTyp.Name})
|
||||||
@@ -61,11 +52,11 @@ func convertDayToTypstDayParts(day models.IWorkDay, user models.User) []typstDay
|
|||||||
return typstDayParts
|
return typstDayParts
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) {
|
func renderPDF(data []typstData) (bytes.Buffer, error) {
|
||||||
var markup bytes.Buffer
|
var markup bytes.Buffer
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
|
|
||||||
if err := typst.InjectValues(&markup, map[string]any{"meta": metadata, "days": days}); err != nil {
|
if err := typst.InjectValues(&markup, map[string]any{"data": data}); err != nil {
|
||||||
return output, err
|
return output, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,18 +64,13 @@ func renderPDF(days []typstDay, metadata typstMetadata) (bytes.Buffer, error) {
|
|||||||
// Show is used to replace the current document with whatever content the template function in `template.typ` returns.
|
// Show is used to replace the current document with whatever content the template function in `template.typ` returns.
|
||||||
markup.WriteString(`
|
markup.WriteString(`
|
||||||
#import "templates/abrechnung.typ": abrechnung
|
#import "templates/abrechnung.typ": abrechnung
|
||||||
#show: doc => abrechnung(meta, days)
|
#for d in data {
|
||||||
|
abrechnung(d.Meta, d.Days)
|
||||||
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
|
// Compile the prepared markup with Typst and write the result it into `output.pdf`.
|
||||||
// f, err := os.Create("output.pdf")
|
|
||||||
// if err != nil {
|
|
||||||
// log.Panicf("Failed to create output file: %v.", err)
|
|
||||||
// }
|
|
||||||
// defer f.Close()
|
|
||||||
//
|
|
||||||
|
|
||||||
// typstCLI := typst.CLI{}
|
|
||||||
typstCLI := typst.DockerExec{
|
typstCLI := typst.DockerExec{
|
||||||
ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
|
ContainerName: helper.GetEnv("TYPST_CONTAINER", "arbeitszeitmessung-doc-creator"),
|
||||||
}
|
}
|
||||||
@@ -113,7 +99,7 @@ func PDFCreateController(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-type", "application/pdf")
|
w.Header().Set("Content-type", "application/pdf")
|
||||||
output.WriteTo(w)
|
output.WriteTo(w)
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
|
||||||
@@ -154,7 +140,7 @@ func createEmployeReport(employee models.User, startDate, endDate time.Time) (by
|
|||||||
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderPDF(typstDays, metadata)
|
return renderPDF([]typstData{{Meta: metadata, Days: typstDays}, {Meta: metadata, Days: typstDays}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func PDFHandler(w http.ResponseWriter, r *http.Request) {
|
func PDFHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -193,7 +179,7 @@ func PDFHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
CurrentTimestamp: time.Now().Format("02.01.2006 - 15:04 Uhr"),
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := renderPDF(typstDays, metadata)
|
output, err := renderPDF([]typstData{{Meta: metadata, Days: typstDays}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Could not create pdf report", slog.Any("Error", err))
|
slog.Warn("Could not create pdf report", slog.Any("Error", err))
|
||||||
}
|
}
|
||||||
@@ -227,3 +213,8 @@ type typstDay struct {
|
|||||||
Overtime string `json:"overtime"`
|
Overtime string `json:"overtime"`
|
||||||
IsFriday bool `json:"is-weekend"`
|
IsFriday bool `json:"is-weekend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type typstData struct {
|
||||||
|
Meta typstMetadata `json:"meta"`
|
||||||
|
Days []typstDay `json:"days"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (p *ParamsParser) ParseStringFallback(key string, fallback string) string {
|
|||||||
return p.urlParams.Get(key)
|
return p.urlParams.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ParamsParser) ParseString(key string, fallback string) (string, error) {
|
func (p *ParamsParser) ParseString(key string) (string, error) {
|
||||||
if !p.urlParams.Has(key) {
|
if !p.urlParams.Has(key) {
|
||||||
return "", &NoValueError{Key: key}
|
return "", &NoValueError{Key: key}
|
||||||
}
|
}
|
||||||
|
|||||||
47
Backend/helper/strings_test.go
Normal file
47
Backend/helper/strings_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGetFirst(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
a any
|
||||||
|
b any
|
||||||
|
want any
|
||||||
|
}{
|
||||||
|
{"ints", 10, 20, 10},
|
||||||
|
{"strings", "first", "second", "first"},
|
||||||
|
{"mixed", "abc", 123, "abc"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := GetFirst(tt.a, tt.b)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("GetFirst(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSecond(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
a any
|
||||||
|
b any
|
||||||
|
want any
|
||||||
|
}{
|
||||||
|
{"ints", 10, 20, 20},
|
||||||
|
{"strings", "first", "second", "second"},
|
||||||
|
{"mixed", "abc", 123, 123},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := GetSecond(tt.a, tt.b)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("GetSecond(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,44 @@ import (
|
|||||||
|
|
||||||
func TestGetMonday(t *testing.T) {
|
func TestGetMonday(t *testing.T) {
|
||||||
isMonday, err := time.Parse(time.DateOnly, "2025-07-14")
|
isMonday, err := time.Parse(time.DateOnly, "2025-07-14")
|
||||||
|
isSunday, err := time.Parse(time.DateOnly, "2025-07-20")
|
||||||
notMonday, err := time.Parse(time.DateOnly, "2025-07-16")
|
notMonday, err := time.Parse(time.DateOnly, "2025-07-16")
|
||||||
if err != nil || isMonday.Equal(notMonday) {
|
if err != nil || isMonday.Equal(notMonday) {
|
||||||
t.Errorf("U stupid? %e", err)
|
t.Errorf("U stupid? %e", err)
|
||||||
}
|
}
|
||||||
if GetMonday(isMonday) != isMonday || GetMonday(notMonday) != isMonday {
|
if GetMonday(isMonday) != isMonday {
|
||||||
t.Error("Wrong date conversion!")
|
t.Error("Wrong date conversion!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if GetMonday(notMonday) != isMonday {
|
||||||
|
t.Error("Wrong date conversion (notMonday)!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if GetMonday(isSunday) != isMonday {
|
||||||
|
t.Error("Wrong date conversion (isSunday)!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatDurationFill(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
duration time.Duration
|
||||||
|
fill bool
|
||||||
|
}{
|
||||||
|
{"2h", time.Duration(120 * time.Minute), true},
|
||||||
|
{"30min", time.Duration(30 * time.Minute), true},
|
||||||
|
{"1h 30min", time.Duration(90 * time.Minute), true},
|
||||||
|
{"-1h 30min", time.Duration(-90 * time.Minute), true},
|
||||||
|
{"0min", 0, true},
|
||||||
|
{"", 0, false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if FormatDurationFill(tc.duration, tc.fill) != tc.name {
|
||||||
|
t.Error("Format missmatch in Formatduration.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatDuration(t *testing.T) {
|
func TestFormatDuration(t *testing.T) {
|
||||||
@@ -22,10 +53,6 @@ func TestFormatDuration(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
duration time.Duration
|
duration time.Duration
|
||||||
}{
|
}{
|
||||||
{"2h", time.Duration(120 * time.Minute)},
|
|
||||||
{"30min", time.Duration(30 * time.Minute)},
|
|
||||||
{"1h 30min", time.Duration(90 * time.Minute)},
|
|
||||||
{"-1h 30min", time.Duration(-90 * time.Minute)},
|
|
||||||
{"", 0},
|
{"", 0},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -37,6 +64,29 @@ func TestFormatDuration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsSameDate(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
dateA string
|
||||||
|
dateB string
|
||||||
|
result bool
|
||||||
|
}{
|
||||||
|
{"2025-12-01 00:00:00", "2025-12-01 00:00:00", true},
|
||||||
|
{"2025-12-03 00:00:00", "2025-12-02 00:00:00", false},
|
||||||
|
{"2025-12-03 23:45:00", "2025-12-03 00:00:00", true},
|
||||||
|
{"2025-12-04 24:12:00", "2025-12-04 00:12:00", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("IsSameDateTest: %s date", tc.dateA), func(t *testing.T) {
|
||||||
|
dateA, _ := time.Parse(time.DateTime, tc.dateA)
|
||||||
|
dateB, _ := time.Parse(time.DateTime, tc.dateB)
|
||||||
|
if IsSameDate(dateA, dateB) != tc.result {
|
||||||
|
t.Errorf("Is SameDate did not match! Result %t", IsSameDate(dateA, dateB))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetWorkingDays(t *testing.T) {
|
func TestGetWorkingDays(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
start string
|
start string
|
||||||
@@ -82,3 +132,41 @@ func TestFormatGermanDayOfWeek(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetKW(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
date time.Time
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "First week of year",
|
||||||
|
date: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC), // Monday
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Middle of year",
|
||||||
|
date: time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
want: 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Last week of year",
|
||||||
|
date: time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||||
|
want: 52,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ISO week crossing into next year",
|
||||||
|
date: time.Date(2020, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||||
|
want: 53,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := GetKW(tt.date)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("GetKW(%v) = %d, want %d", tt.date, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
26
Backend/helper/types_test.go
Normal file
26
Backend/helper/types_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBoolToInt(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
value bool
|
||||||
|
res int
|
||||||
|
res8 int8
|
||||||
|
}{
|
||||||
|
{true, 1, 1},
|
||||||
|
{false, 0, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("BoolToInt value: %t", tc.value), func(t *testing.T) {
|
||||||
|
if BoolToInt(tc.value) != tc.res || BoolToInt8(tc.value) != tc.res8 {
|
||||||
|
t.Error("How could you... mess up bool to int")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
112
Backend/helper/web_test.go
Normal file
112
Backend/helper/web_test.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package helper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alexedwards/scs/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetCors_WhenNoCorsTrue(t *testing.T) {
|
||||||
|
os.Setenv("NO_CORS", "true")
|
||||||
|
defer os.Unsetenv("NO_CORS")
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
SetCors(rr)
|
||||||
|
|
||||||
|
h := rr.Header()
|
||||||
|
|
||||||
|
if h.Get("Access-Control-Allow-Origin") != "*" {
|
||||||
|
t.Errorf("expected Access-Control-Allow-Origin to be '*', got %q", h.Get("Access-Control-Allow-Origin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Get("Access-Control-Allow-Methods") != "*" {
|
||||||
|
t.Errorf("expected Access-Control-Allow-Methods to be '*', got %q", h.Get("Access-Control-Allow-Methods"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Get("Access-Control-Allow-Headers") != "*" {
|
||||||
|
t.Errorf("expected Access-Control-Allow-Headers to be '*', got %q", h.Get("Access-Control-Allow-Headers"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCors_WhenNoCorsFalse(t *testing.T) {
|
||||||
|
os.Setenv("NO_CORS", "false")
|
||||||
|
defer os.Unsetenv("NO_CORS")
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
SetCors(rr)
|
||||||
|
|
||||||
|
h := rr.Header()
|
||||||
|
if h.Get("Access-Control-Allow-Origin") != "" ||
|
||||||
|
h.Get("Access-Control-Allow-Methods") != "" ||
|
||||||
|
h.Get("Access-Control-Allow-Headers") != "" {
|
||||||
|
t.Errorf("CORS headers should not be set when NO_CORS=false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiresLogin_DebugMode_NoRedirect(t *testing.T) {
|
||||||
|
os.Setenv("GO_ENV", "debug")
|
||||||
|
defer os.Unsetenv("GO_ENV")
|
||||||
|
|
||||||
|
session := scs.New()
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
RequiresLogin(session, rr, req)
|
||||||
|
|
||||||
|
if rr.Result().StatusCode == http.StatusSeeOther {
|
||||||
|
t.Errorf("expected no redirect in debug mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TestRequiresLogin_UserExists_NoRedirect(t *testing.T) {
|
||||||
|
// os.Setenv("GO_ENV", "production")
|
||||||
|
// defer os.Unsetenv("GO_ENV")
|
||||||
|
|
||||||
|
// session := scs.New()
|
||||||
|
|
||||||
|
// req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
// ctx, err := session.Load(req.Context(), "")
|
||||||
|
// if err != nil {
|
||||||
|
// t.Fatalf("session load error: %v", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ctx = session.Put(ctx, "user", "123")
|
||||||
|
// req = req.WithContext(context.WithValue(ctx, "session", session))
|
||||||
|
|
||||||
|
// rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// yourpkg.RequiresLogin(session, rr, req)
|
||||||
|
|
||||||
|
// if rr.Result().StatusCode == http.StatusSeeOther {
|
||||||
|
// t.Errorf("expected no redirect when user exists")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func TestRequiresLogin_NoUser_Redirects(t *testing.T) {
|
||||||
|
// os.Setenv("GO_ENV", "production")
|
||||||
|
// defer os.Unsetenv("GO_ENV")
|
||||||
|
|
||||||
|
// session := scs.New()
|
||||||
|
|
||||||
|
// req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
// req = req.WithContext(context.WithValue(req.Context(), "session", session))
|
||||||
|
|
||||||
|
// rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// RequiresLogin(session, rr, req)
|
||||||
|
|
||||||
|
// if rr.Result().StatusCode != http.StatusSeeOther {
|
||||||
|
// t.Errorf("expected redirect when user does not exist, got %d", rr.Result().StatusCode)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// location := rr.Result().Header.Get("Location")
|
||||||
|
// if location != "/user/login" {
|
||||||
|
// t.Errorf("expected redirect to /user/login, got %q", location)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -46,7 +46,8 @@ func main() {
|
|||||||
server.HandleFunc("/time/new", endpoints.TimeCreateHandler)
|
server.HandleFunc("/time/new", endpoints.TimeCreateHandler)
|
||||||
server.Handle("/absence", ParamsMiddleware(endpoints.AbsencHandler))
|
server.Handle("/absence", ParamsMiddleware(endpoints.AbsencHandler))
|
||||||
server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler))
|
server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler))
|
||||||
server.HandleFunc("/logout", endpoints.LogoutHandler)
|
server.HandleFunc("/auto/logout", endpoints.LogoutHandler)
|
||||||
|
server.HandleFunc("/auto/kurzarbeit", endpoints.KurzarbeitFillHandler)
|
||||||
server.HandleFunc("/user/{action}", endpoints.UserHandler)
|
server.HandleFunc("/user/{action}", endpoints.UserHandler)
|
||||||
// server.HandleFunc("/user/login", endpoints.LoginHandler)
|
// server.HandleFunc("/user/login", endpoints.LoginHandler)
|
||||||
// server.HandleFunc("/user/settings", endpoints.UserSettingsHandler)
|
// server.HandleFunc("/user/settings", endpoints.UserSettingsHandler)
|
||||||
|
|||||||
@@ -57,6 +57,34 @@ func (u *User) GetReportedOvertime() (time.Duration, error) {
|
|||||||
return overtime, nil
|
return overtime, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAllUsers() ([]User, error) {
|
||||||
|
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname,arbeitszeit_per_tag, arbeitszeit_per_woche FROM s_personal_daten;`))
|
||||||
|
var users []User
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error preparing query statement %v\n", err)
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
defer qStr.Close()
|
||||||
|
rows, err := qStr.Query()
|
||||||
|
if err != nil {
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
|
||||||
|
var user User
|
||||||
|
if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name, &user.ArbeitszeitPerTag, &user.ArbeitszeitPerWoche); err != nil {
|
||||||
|
log.Println("Error creating user!", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) GetAll() ([]User, error) {
|
func (u *User) GetAll() ([]User, error) {
|
||||||
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
|
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM s_personal_daten;`))
|
||||||
var users []User
|
var users []User
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ CREATE TABLE "s_abwesenheit_typen" (
|
|||||||
"arbeitszeit_equivalent" float4 NOT NULL
|
"arbeitszeit_equivalent" float4 NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; 1=Arbeitszeit auffüllen; 2=Arbeitszeit austauschen';
|
COMMENT ON COLUMN "s_abwesenheit_typen"."arbeitszeit_equivalent" IS '0=keine Arbeitszeit; -1=Arbeitszeit auffüllen; <=1 => Arbeitszeit';
|
||||||
|
|
||||||
-- Adds crypto extension
|
-- Adds crypto extension
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
columns: (3fr, .65fr),
|
columns: (3fr, .65fr),
|
||||||
align: left + horizon,
|
align: left + horizon,
|
||||||
inset: .5em,
|
inset: .5em,
|
||||||
[#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("static/logo.png")],
|
[#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("/static/logo.png")],
|
||||||
[Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp],
|
[Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp],
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Arbeitszeitmessung
|
# Arbeitszeitmessung
|
||||||
|
|
||||||
[](https://sonar.letsstein.de/dashboard?id=arbeitszeitmessung)
|
[](https://sonar.letsstein.de/dashboard?id=arbeitszeitmessung)
|
||||||
|
|
||||||
bis jetzt ein einfaches Backend mit PostgreSQL Datenbank und GO Webserver um Arbeitszeitbuchungen per HTTP PUT einzufügen
|
bis jetzt ein einfaches Backend mit PostgreSQL Datenbank und GO Webserver um Arbeitszeitbuchungen per HTTP PUT einzufügen
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user