diff --git a/Backend/database.go b/Backend/database.go index 041af65..3ef0dc1 100644 --- a/Backend/database.go +++ b/Backend/database.go @@ -25,13 +25,14 @@ func OpenDatabase() (models.IDatabase, error) { } func Migrate() error { + return nil dbHost := helper.GetEnv("POSTGRES_HOST", "localhost") dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung") // dbUser := helper.GetEnv("POSTGRES_USER", "api_nutzer") dbPassword := helper.GetEnv("POSTGRES_PASSWORD", "password") dbTz := helper.GetEnv("TZ", "Europe/Berlin") - migrations := helper.GetEnv("MIGRATIONS_DIR", "../migrations") + migrations := helper.GetEnv("MIGRATIONS_PATH", "../migrations") connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=%s", "migrate", dbPassword, dbHost, dbName, dbTz) m, err := migrate.New(fmt.Sprintf("file://%s", migrations), connStr) diff --git a/Backend/endpoints/time.go b/Backend/endpoints/time.go index e3b0193..393044a 100644 --- a/Backend/endpoints/time.go +++ b/Backend/endpoints/time.go @@ -160,19 +160,13 @@ func updateBooking(w http.ResponseWriter, r *http.Request) { newBooking := (*models.Booking).New(nil, user.CardUID, 0, int16(check_in_out), 1) newBooking.Timestamp = timestamp - err = newBooking.InsertWithTimestamp() - if err != nil { - log.Printf("Error inserting booking %v -> %v\n", newBooking, err) + if newBooking.Verify() { + err = newBooking.InsertWithTimestamp() + if err != nil { + log.Printf("Error inserting booking %v -> %v\n", newBooking, err) + } } case "change": - // absenceType, err := strconv.Atoi(r.FormValue("absence")) - // if err != nil { - // log.Println("Error parsing absence type.", err) - // absenceType = 0 - // } - // if absenceType != 0 { - // createAbsence(absenceType, user, loc, r) - // } for index, possibleBooking := range r.PostForm { if len(index) > 7 && index[:7] == "booking" { booking_id, err := strconv.Atoi(index[8:]) diff --git a/Backend/models/booking.go b/Backend/models/booking.go index 28b638a..55e3aca 100644 --- a/Backend/models/booking.go +++ b/Backend/models/booking.go @@ -128,6 +128,9 @@ func (b *Booking) InsertWithTimestamp() error { if b.Timestamp.IsZero() { return b.Insert() } + if !checkLastBooking(*b) { + return SameBookingError{} + } stmt, err := DB.Prepare((`INSERT INTO anwesenheit (card_uid, geraet_id, check_in_out, anwesenheit_typ, timestamp) VALUES ($1, $2, $3, $4, $5) RETURNING counter_id`)) if err != nil { return err diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index c995275..12c6669 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -175,22 +175,39 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay { qStr, err := DB.Prepare(` WITH all_days AS ( SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date), - ordered_bookings AS ( - SELECT - a.timestamp::DATE AS work_date, - a.timestamp, - a.check_in_out, - a.counter_id, - a.anwesenheit_typ, - sat.anwesenheit_name AS anwesenheit_typ_name, - LAG(a.timestamp) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_timestamp, - LAG(a.check_in_out) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_check - FROM anwesenheit a - LEFT JOIN s_anwesenheit_typen sat ON a.anwesenheit_typ = sat.anwesenheit_id - WHERE a.card_uid = $1 - AND a.timestamp::DATE >= $2 - AND a.timestamp::DATE <= $3 - ) + normalized_bookings AS ( + SELECT * + FROM ( + SELECT + a.card_uid, + a.timestamp, + a.timestamp::DATE AS work_date, + a.check_in_out, + a.counter_id, + a.anwesenheit_typ, + sat.anwesenheit_name AS anwesenheit_typ_name, + LAG(a.check_in_out) OVER ( + PARTITION BY a.card_uid, a.timestamp::DATE + ORDER BY a.timestamp + ) AS prev_check + FROM anwesenheit a + LEFT JOIN s_anwesenheit_typen sat + ON a.anwesenheit_typ = sat.anwesenheit_id + WHERE a.card_uid = $1 + AND a.timestamp::DATE >= $2 + AND a.timestamp::DATE <= $3 + ) t + WHERE prev_check IS NULL OR prev_check <> check_in_out + ), + ordered_bookings AS ( + SELECT + *, + LAG(timestamp) OVER ( + PARTITION BY card_uid, work_date + ORDER BY timestamp + ) AS prev_timestamp + FROM normalized_bookings + ) SELECT d.work_date, COALESCE(MIN(b.timestamp), NOW()) AS time_from, @@ -228,6 +245,62 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay { GROUP BY d.work_date ORDER BY d.work_date ASC;`) + // qStr, err := DB.Prepare(` + // WITH all_days AS ( + // SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date), + // ordered_bookings AS ( + // SELECT + // a.timestamp::DATE AS work_date, + // a.timestamp, + // a.check_in_out, + // a.counter_id, + // a.anwesenheit_typ, + // sat.anwesenheit_name AS anwesenheit_typ_name, + // LAG(a.timestamp) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_timestamp, + // LAG(a.check_in_out) OVER (PARTITION BY a.card_uid, a.timestamp::DATE ORDER BY a.timestamp) AS prev_check + // FROM anwesenheit a + // LEFT JOIN s_anwesenheit_typen sat ON a.anwesenheit_typ = sat.anwesenheit_id + // WHERE a.card_uid = $1 + // AND a.timestamp::DATE >= $2 + // AND a.timestamp::DATE <= $3 + // ) + // SELECT + // d.work_date, + // COALESCE(MIN(b.timestamp), NOW()) AS time_from, + // COALESCE(MAX(b.timestamp), NOW()) AS time_to, + // COALESCE( + // EXTRACT(EPOCH FROM SUM( + // CASE + // WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 254) + // THEN b.timestamp - b.prev_timestamp + // ELSE INTERVAL '0' + // END + // )), 0 + // ) AS total_work_seconds, + // COALESCE( + // EXTRACT(EPOCH FROM SUM( + // CASE + // WHEN b.prev_check IN (2, 4, 254) AND b.check_in_out IN (1, 3) + // THEN b.timestamp - b.prev_timestamp + // ELSE INTERVAL '0' + // END + // )), 0 + // ) AS total_pause_seconds, + // COALESCE(jsonb_agg(jsonb_build_object( + // 'check_in_out', b.check_in_out, + // 'timestamp', b.timestamp, + // 'counter_id', b.counter_id, + // 'anwesenheit_typ', b.anwesenheit_typ, + // 'anwesenheit_typ', jsonb_build_object( + // 'anwesenheit_id', b.anwesenheit_typ, + // 'anwesenheit_name', b.anwesenheit_typ_name + // ) + // ) ORDER BY b.timestamp), '[]'::jsonb) AS bookings + // FROM all_days d + // LEFT JOIN ordered_bookings b ON d.work_date = b.work_date + // GROUP BY d.work_date + // ORDER BY d.work_date ASC;`) + if err != nil { log.Println("Error preparing SQL statement", err) return workDays diff --git a/Docker/docker-compose.dev.yml b/Docker/docker-compose.dev.yml index df1ca60..2fc30dd 100644 --- a/Docker/docker-compose.dev.yml +++ b/Docker/docker-compose.dev.yml @@ -2,7 +2,7 @@ name: arbeitszeitmessung-dev services: db: volumes: - - ../DDB:/var/lib/postgresql/data + - ${POSTGRES_PATH}:/var/lib/postgresql/data - ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d ports: - 5432:5432 diff --git a/Docker/env.example b/Docker/env.example index 4d667fa..64f40a6 100644 --- a/Docker/env.example +++ b/Docker/env.example @@ -3,9 +3,11 @@ POSTGRES_PASSWORD=very_secure # Postgres ADMIN Passwort POSTGRES_API_USER=api_nutzer # Postgres API Nutzername (für Arbeitszeitmessung) POSTGRES_API_PASS=password # Postgres API Passwort (für Arbeitszeitmessung) POSTGRES_PATH=__ROOT__/DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...) -LOG_PATH=__ROOT__/logs # Pfad für Audit Logs POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name -POSTGRES_PORT=127.0.0.1:5432 # Postgres Port will not be exposed by default. regex:^[0-9]{1,5}$ +POSTGRES_PORT=127.0.0.1:5432 # Postgres Port normalerweise nicht freigegeben. regex:^[0-9]{1,5}$ +MIGRATIONS_PATH=__ROOT__/migrations # Pfad zu DB migrations (wenn nicht verändert wurde, bei default bleiben) TZ=Europe/Berlin # Zeitzone API_TOKEN=dont_access # API Token für ESP Endpoints -WEB_PORT=8000 # Port from which Arbeitszeitmessung should be accessable regex:^[0-9]{1,5}$ +WEB_PORT=8000 # Port unter welchem Webserver erreichbar ist. regex:^[0-9]{1,5}$ +LOG_PATH=__ROOT__/logs # Pfad für Audit Logs +LOG_LEVEL=warn # Welche Log-Nachrichten werden in der Konsole erscheinen