From 3322f7e9bc7baa712771179885df346a3705516f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Wed, 7 Jan 2026 19:55:00 +0100 Subject: [PATCH 1/5] updated install script with cron jobs --- Cron/autoBackup.sh | 6 ++ Cron/autoHolidays.sh | 3 + Cron/autoLogout.sh | 4 + Docker/env.example | 5 +- DocumentCreator/Dockerfile | 7 -- DocumentCreator/static/logo.png | Bin 17585 -> 0 bytes DocumentCreator/templates/abrechnung.typ | 97 ----------------------- install.sh | 82 ++++++++++++++++++- 8 files changed, 95 insertions(+), 109 deletions(-) create mode 100755 Cron/autoBackup.sh create mode 100755 Cron/autoHolidays.sh create mode 100755 Cron/autoLogout.sh delete mode 100644 DocumentCreator/Dockerfile delete mode 100644 DocumentCreator/static/logo.png delete mode 100644 DocumentCreator/templates/abrechnung.typ diff --git a/Cron/autoBackup.sh b/Cron/autoBackup.sh new file mode 100755 index 0000000..5430a1d --- /dev/null +++ b/Cron/autoBackup.sh @@ -0,0 +1,6 @@ +# cron-timing: 05 01 * * 1 +container_name="arbeitszeitmessung-main-db-1" +filename=backup-$(date '+%d%m%Y').sql +database_name=__DATABASE__ +docker exec $container_name pg_dump $database_name > /home/pi/arbeitszeitmessung-backup/$filename +echo "created backup file: "$filename diff --git a/Cron/autoHolidays.sh b/Cron/autoHolidays.sh new file mode 100755 index 0000000..50607b4 --- /dev/null +++ b/Cron/autoHolidays.sh @@ -0,0 +1,3 @@ +# Calls endpoint to write all public Holidays for the current year inside a database. +port=__PORT__ +curl localhost:$port/auto/feiertage diff --git a/Cron/autoLogout.sh b/Cron/autoLogout.sh new file mode 100755 index 0000000..75acb96 --- /dev/null +++ b/Cron/autoLogout.sh @@ -0,0 +1,4 @@ +# cron-timing: 55 23 * * * +# Calls endpoint to log out all users, still logged in for today +port=__PORT__ +curl localhost:$port/auto/logout diff --git a/Docker/env.example b/Docker/env.example index ab2147f..4d667fa 100644 --- a/Docker/env.example +++ b/Docker/env.example @@ -2,11 +2,10 @@ POSTGRES_USER=root # Postgres ADMIN Nutzername 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=../DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...) -LOG_PATH=../logs # Pfad für Logdatein +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}$ 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}$ -TYPST_CONTAINER=arbeitszeitmessung-doc-creator # Name of the pdf compiler container diff --git a/DocumentCreator/Dockerfile b/DocumentCreator/Dockerfile deleted file mode 100644 index e132f75..0000000 --- a/DocumentCreator/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM ghcr.io/typst/typst:0.14.0 - -WORKDIR /app -COPY ./templates /app/templates -COPY ./static /app/static - -ENTRYPOINT ["sh", "-c", "while true; do sleep 3600; done"] diff --git a/DocumentCreator/static/logo.png b/DocumentCreator/static/logo.png deleted file mode 100644 index 53694f96af0ec6289ac91689eed61ed30607890c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17585 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 b{{ strong(h)) - ) -} - - -#let abrechnung(meta, days) = { - set page(paper: "a4", margin: (x:1.5cm, y:2.25cm), - footer:[#grid( - columns: (3fr, .65fr), - align: left + horizon, - inset: .5em, - [#meta.EmployeeName -- #meta.TimeRange], grid.cell(rowspan: 2)[#image("/static/logo.png")], - [Arbeitszeitrechnung maschinell erstellt am #meta.CurrentTimestamp], - ) - ]) - set text(font: "Noto Sans", size:10pt, fill: luma(10%)) - set table( - stroke: 0.5pt + luma(10%), - inset: .5em, - align: center + horizon, - ) - show text: it => { - if it.text == "0min"{ - text(oklch(70.8%, 0, 0deg))[#it] - }else if it.text.starts-with("-"){ - text(red)[#it] - }else{ - it - } - } - - - [= Abrechnung Arbeitszeit -- #meta.EmployeeName] - - [Zeitraum: #meta.TimeRange] - - table( - columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, .875fr, 1.25fr), - fill: (x, y) => - if y == 0 { oklch(87%, 0, 0deg) }, - table-header( - [Datum], [Kommen], [Gehen], [Arbeitsart], [Stunden], [Kurzarbeit], [Pause], [Überstunden] - ), - .. for day in days { - ( - [#day.Date], - if day.DayParts.len() == 0{ - table.cell(colspan: 3)[Keine Buchungen] - }else if day.DayParts.len() == 1 and not day.DayParts.first().IsWorkDay{ - table.cell(colspan: 3)[#day.DayParts.first().WorkType] - } - else { - table.cell(colspan: 3, inset: 0em)[ - #table( - columns: (1fr, 1fr, 1fr), - .. for Zeit in day.DayParts { - ( - if Zeit.IsWorkDay{ - ( - table.cell()[#Zeit.BookingFrom], - table.cell()[#Zeit.BookingTo], - table.cell()[#Zeit.WorkType], - ) - }else{ - (table.cell(colspan: 3)[#Zeit.WorkType],) - } - ) - }, - ) - ] - }, - [#day.Worktime], - [#day.Kurzarbeit], - [#day.Pausetime], - [#day.Overtime], - ) - if day.IsFriday { - ( table.cell(colspan: 8, fill: oklch(87%, 0, 0deg))[Wochenende], ) // note the trailing comma - } - } - ) - - table( - columns: (3fr, 1fr), - align: right, - inset: (x: .25em, y:.75em), - stroke: none, - table.hline(start: 0, end: 2, stroke: stroke(dash:"dashed", thickness:.5pt)), - [Arbeitszeit :], table.cell(align: left)[#meta.WorkTime], - [Kurzarbeit :], table.cell(align: left)[#meta.Kurzarbeit], - [Überstunden :], table.cell(align: left)[#meta.Overtime], - [Überstunden lfd. :],table.cell(align: left)[#meta.OvertimeTotal], - table.hline(start: 0, end: 2), -) -} diff --git a/install.sh b/install.sh index 114c7b5..c3c12e6 100755 --- a/install.sh +++ b/install.sh @@ -4,6 +4,10 @@ set -e envFile=Docker/.env envExample=Docker/env.example +autoBackupScript=Cron/autoBackup.sh +autoHolidaysScript=Cron/autoHolidays.sh +autoLogoutScript=Cron/autoLogout.sh + echo "Checking Docker installation..." if ! command -v docker >/dev/null 2>&1; then echo "Docker not found. Install Docker? [y/N]" @@ -18,12 +22,16 @@ else echo "Docker is already installed." fi +########################################################################### + echo "Checking Docker Compose..." if ! docker compose version >/dev/null 2>&1; then echo "Docker Compose plugin missing. You may need to update Docker." exit 1 fi +########################################################################### + echo "Preparing .env file..." if [ ! -f $envFile ]; then if [ -f $envExample ]; then @@ -44,6 +52,9 @@ if [ ! -f $envFile ]; then raw_val=$(printf "%s" "$rest" | sed 's/ *#.*//') default_value=$(printf "%s" "$raw_val" | sed 's/"//g') + # Replace __ROOT__ with script pwd + default_value="${default_value/__ROOT__/$(pwd)}" + regex="" if [[ "$comment" =~ regex:(.*)$ ]]; then regex="${BASH_REMATCH[1]}" @@ -96,13 +107,80 @@ else echo "Using existing .env. (found at $envFile)" fi +########################################################################### + +LOG_PATH=$(grep -E '^LOG_PATH=' $envFile | cut -d= -f2) +if [ -z "$LOG_PATH" ]; then + echo "LOG_PATH not found in .env using default $(pwd)/logs" + LOG_PATH=$(pwd)/logs +else + LOG_PATH=Docker/$LOG_PATH +fi +mkdir -p $LOG_PATH +echo "Created logs folder at $LOG_PATH" + +########################################################################### + +echo -e "\n\n" echo "Start containers with docker compose up -d? [y/N]" -read -r start_containers +read -r start_containersmkdi if [[ "$start_containers" =~ ^[Yy]$ ]]; then + cd Docker - mkdir ../logs docker compose up -d echo "Containers started." else echo "You can start them manually with: docker compose up -d" fi + +########################################################################### + +echo -e "\n\n" +echo "Setup Crontab for automatic logout, backup and holiday creation? [y/N]" +read -r setup_cron +if [[ "$setup_cron" =~ ^[Yy]$ ]]; then + WEB_PORT=$(grep -E '^WEB_PORT=' $envFile | cut -d= -f2) + if [ -z "$WEB_PORT" ]; then + echo "WEB_PORT not found in .env using default 8000" + WEB_PORT=8000 + fi + + POSTGRES_DB=$(grep -E '^POSTGRES_DB=' $envFile | cut -d= -f2) + if [ -z "$POSTGRES_DB" ]; then + echo "arbeitszeitmessung not found in .env using default arbeitszeitmessung" + POSTGRES_DB="arbeitszeitmessung" + fi + + sed -i "s/__PORT__/$WEB_PORT/" $autoHolidaysScript + sed -i "s/__PORT__/$WEB_PORT/" $autoLogoutScript + sed -i "s/__DATABASE__/$POSTGRES_DB/" $autoBackupScript + + chmod +x $autoBackupScript $autoHolidaysScript $autoLogoutScript + + # echo "Scripts build with PORT=$WEB_PORT and DATABSE=$POSTGRES_DB!" + echo "Adding rules to crontab." + + cron_commands=$(mktemp /tmp/arbeitszeitmessung-cron.XXX) + + for file in Cron/*; do + cron_timing=$(grep -E '^# cron-timing:' "$file" | sed 's/^# cron-timing:[[:space:]]*//') + + if [ -z "$cron_timing" ]; then + echo "No cron-timing found in $file, so it's not added to crontab." + continue + fi + + ( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab - + echo "Added entry to crontab: $cron_timing $(pwd)/$file." + done + + if systemctl is-active --quiet cron.service ; then + echo "cron.service is running. Everything should be fine now." + else + echo "cron.service is not running. Please start and enable cron.service." + echo "For how to start a service, see: https://wiki.ubuntuusers.de/systemd/systemctl UNITNAME will be cron.service" + fi + +else + echo "Please setup cron manually by executing crontab -e and adding all files from inside the Cron directory!" +fi -- 2.49.1 From 1daf4db16766a9c043454264c95783a2381a3e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Sat, 17 Jan 2026 21:41:46 +0100 Subject: [PATCH 2/5] fixed premission problem after making migrations executed by go --- Backend/database.go | 30 ++++++++++ Backend/main.go | 7 +++ DB/initdb/01_create_user.sh | 55 +++++++++++++++++++ DB/initdb/02_sample_data.sql | 8 --- DB/initdb/03_create_user.sh | 21 ------- Docker/docker-compose.dev.yml | 4 +- migrations/20250901201159_initial.up.sql | 12 ++++ ...20250901201710_triggers_extension.down.sql | 5 -- .../20250901201710_triggers_extension.up.sql | 2 - migrations/20251217215955_feiertage.up.sql | 2 + DB/initdb/01_schema.sql => schema.sql | 0 11 files changed, 108 insertions(+), 38 deletions(-) create mode 100755 DB/initdb/01_create_user.sh delete mode 100755 DB/initdb/02_sample_data.sql delete mode 100755 DB/initdb/03_create_user.sh rename DB/initdb/01_schema.sql => schema.sql (100%) diff --git a/Backend/database.go b/Backend/database.go index 8394d1f..041af65 100644 --- a/Backend/database.go +++ b/Backend/database.go @@ -5,7 +5,11 @@ import ( "arbeitszeitmessung/models" "database/sql" "fmt" + "log/slog" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + _ "github.com/golang-migrate/migrate/v4/source/file" _ "github.com/lib/pq" ) @@ -19,3 +23,29 @@ func OpenDatabase() (models.IDatabase, error) { connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=%s", dbUser, dbPassword, dbHost, dbName, dbTz) return sql.Open("postgres", connStr) } + +func Migrate() error { + 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") + + 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) + if err != nil { + return err + } + + slog.Info("Connected to database. Running migrations now.") + + // Migrate all the way up ... + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + return err + } + + slog.Info("Finished migrations starting webserver.") + return nil +} diff --git a/Backend/main.go b/Backend/main.go index 731173c..e083361 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -44,6 +44,13 @@ func main() { models.DB, err = OpenDatabase() if err != nil { slog.Error("Error while opening the database", "Error", err) + return + } + + err = Migrate() + if err != nil { + slog.Error("Failed to migrate the database to newest version", "Error", err) + return } fs := http.FileServer(http.Dir("./static")) diff --git a/DB/initdb/01_create_user.sh b/DB/initdb/01_create_user.sh new file mode 100755 index 0000000..9536063 --- /dev/null +++ b/DB/initdb/01_create_user.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -e # Exit on error + +echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API user $POSTGRES_API_USER" + + + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE ROLE migrate LOGIN ENCRYPTED PASSWORD '$POSTGRES_PASSWORD'; + GRANT USAGE, CREATE ON SCHEMA public TO migrate; +EOSQL + +# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + +# GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report, s_feiertage TO $POSTGRES_API_USER; +# GRANT DELETE ON abwesenheit TO $POSTGRES_API_USER; +# GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen, s_feiertage TO $POSTGRES_API_USER; +# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER; +# EOSQL + +echo "User creation and permissions setup complete!" + + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + +-- privilege roles +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_base') THEN + CREATE ROLE app_base NOLOGIN; + END IF; +END +\$\$; + +-- dynamic login role +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '$POSTGRES_API_USER') THEN + CREATE ROLE $POSTGRES_API_USER + LOGIN + ENCRYPTED PASSWORD '$POSTGRES_API_PASS'; + END IF; +END +\$\$; + +-- grant base privileges +GRANT app_base TO $POSTGRES_API_USER; +GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER; +GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER; + +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +EOSQL + +# psql -v ON_ERROR_STOP=1 --username root --dbname arbeitszeitmessung diff --git a/DB/initdb/02_sample_data.sql b/DB/initdb/02_sample_data.sql deleted file mode 100755 index ea7fcf7..0000000 --- a/DB/initdb/02_sample_data.sql +++ /dev/null @@ -1,8 +0,0 @@ -INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_per_woche", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES -(123, 't', 'Kim', 'Mustermensch', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'aaaa-aaaa', 1, 8, 40, '07:00:00', '20:00:00', 0); - -INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES -(123, crypt('max_pass', gen_salt('bf'))); - -INSERT INTO "s_anwesenheit_typen" ("anwesenheit_id", "anwesenheit_name") VALUES (1, 'Büro'); -INSERT INTO "s_abwesenheit_typen" ("abwesenheit_id", "abwesenheit_name", "arbeitszeit_equivalent") VALUES (1, 'Urlaub', 100), (2, 'Krank', 100), (3, 'Kurzarbeit', -1), (4, 'Urlaub untertags', 50); diff --git a/DB/initdb/03_create_user.sh b/DB/initdb/03_create_user.sh deleted file mode 100755 index 42be086..0000000 --- a/DB/initdb/03_create_user.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e # Exit on error - -echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API user $POSTGRES_API_USER" - -psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL - CREATE USER $POSTGRES_API_USER WITH ENCRYPTED PASSWORD '$POSTGRES_API_PASS'; -EOSQL - -psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL - GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER; - GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER; - GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report, s_feiertage TO $POSTGRES_API_USER; - GRANT DELETE ON abwesenheit TO $POSTGRES_API_USER; - GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen, s_feiertage TO $POSTGRES_API_USER; - GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER; -EOSQL - -echo "User creation and permissions setup complete!" - -# psql -v ON_ERROR_STOP=1 --username root --dbname arbeitszeitmessung diff --git a/Docker/docker-compose.dev.yml b/Docker/docker-compose.dev.yml index b5a413b..df1ca60 100644 --- a/Docker/docker-compose.dev.yml +++ b/Docker/docker-compose.dev.yml @@ -2,8 +2,8 @@ name: arbeitszeitmessung-dev services: db: volumes: - - ${POSTGRES_PATH}:/var/lib/postgresql/data - # - ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d + - ../DDB:/var/lib/postgresql/data + - ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d ports: - 5432:5432 diff --git a/migrations/20250901201159_initial.up.sql b/migrations/20250901201159_initial.up.sql index 5144756..23f4a3f 100644 --- a/migrations/20250901201159_initial.up.sql +++ b/migrations/20250901201159_initial.up.sql @@ -1,3 +1,11 @@ +ALTER DEFAULT PRIVILEGES FOR ROLE migrate +IN SCHEMA public +GRANT SELECT ON TABLES TO app_base; + +ALTER DEFAULT PRIVILEGES FOR ROLE migrate +IN SCHEMA public +GRANT USAGE, SELECT ON SEQUENCES TO app_base; + -- create "abwesenheit" table CREATE TABLE "abwesenheit" ( "counter_id" bigserial NOT NULL, @@ -6,6 +14,7 @@ CREATE TABLE "abwesenheit" ( "datum" timestamptz NULL DEFAULT (now())::date, PRIMARY KEY ("counter_id") ); + -- create "anwesenheit" table CREATE TABLE "anwesenheit" ( "counter_id" bigserial NOT NULL, @@ -55,3 +64,6 @@ CREATE TABLE "wochen_report" ( PRIMARY KEY ("id"), CONSTRAINT "wochen_report_personal_nummer_woche_start_key" UNIQUE ("personal_nummer", "woche_start") ); + +GRANT INSERT, UPDATE ON abwesenheit, anwesenheit, wochen_report, user_password TO app_base; +GRANT DELETE ON abwesenheit to app_base; diff --git a/migrations/20250901201710_triggers_extension.down.sql b/migrations/20250901201710_triggers_extension.down.sql index 4260320..9bca733 100644 --- a/migrations/20250901201710_triggers_extension.down.sql +++ b/migrations/20250901201710_triggers_extension.down.sql @@ -3,8 +3,3 @@ DROP FUNCTION update_zuletzt_geandert; DROP TRIGGER IF EXISTS pass_hash_update ON user_password; - - --- revert: Adds crypto extension - -DROP EXTENSION IF EXISTS pgcrypto; diff --git a/migrations/20250901201710_triggers_extension.up.sql b/migrations/20250901201710_triggers_extension.up.sql index 5e8ad1f..0c9d936 100644 --- a/migrations/20250901201710_triggers_extension.up.sql +++ b/migrations/20250901201710_triggers_extension.up.sql @@ -17,5 +17,3 @@ FOR EACH ROW EXECUTE FUNCTION update_zuletzt_geandert(); -- Adds crypto extension - -CREATE EXTENSION IF NOT EXISTS pgcrypto; diff --git a/migrations/20251217215955_feiertage.up.sql b/migrations/20251217215955_feiertage.up.sql index 69072a0..735d7f1 100644 --- a/migrations/20251217215955_feiertage.up.sql +++ b/migrations/20251217215955_feiertage.up.sql @@ -11,3 +11,5 @@ CREATE TABLE "s_feiertage" ( ); -- create index "feiertage_unique_pro_jahr" to table: "s_feiertage" CREATE UNIQUE INDEX "feiertage_unique_pro_jahr" ON "s_feiertage" ((EXTRACT(year FROM datum)), "name"); + +GRANT INSERT, UPDATE ON s_feiertage TO app_base; diff --git a/DB/initdb/01_schema.sql b/schema.sql similarity index 100% rename from DB/initdb/01_schema.sql rename to schema.sql -- 2.49.1 From cfd77ae28d207b15c999a3968ec3e36f0413527a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Sat, 17 Jan 2026 22:25:25 +0100 Subject: [PATCH 3/5] fixed #70 + made db script ignore double bookings --- Backend/database.go | 3 +- Backend/endpoints/time.go | 16 ++---- Backend/models/booking.go | 3 + Backend/models/workDay.go | 105 ++++++++++++++++++++++++++++------ Docker/docker-compose.dev.yml | 2 +- Docker/env.example | 8 ++- 6 files changed, 105 insertions(+), 32 deletions(-) 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 -- 2.49.1 From 502955d32ff2c92c756e3727d12fed4b87d75bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Sat, 17 Jan 2026 22:53:35 +0100 Subject: [PATCH 4/5] added migrations back + removed distracting log message --- Backend/database.go | 1 - Backend/main.go | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Backend/database.go b/Backend/database.go index 3ef0dc1..7f3abf5 100644 --- a/Backend/database.go +++ b/Backend/database.go @@ -25,7 +25,6 @@ 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") diff --git a/Backend/main.go b/Backend/main.go index e083361..c13fcde 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -5,6 +5,7 @@ import ( "arbeitszeitmessung/helper" "arbeitszeitmessung/models" "context" + "database/sql" "log/slog" "net/http" "os" @@ -47,6 +48,8 @@ func main() { return } + defer models.DB.(*sql.DB).Close() + err = Migrate() if err != nil { slog.Error("Failed to migrate the database to newest version", "Error", err) @@ -86,7 +89,9 @@ func ParamsMiddleware(next http.HandlerFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { queryParams := r.URL.Query() ctx := context.WithValue(r.Context(), "urlParams", queryParams) - slog.Debug("ParamsMiddleware added urlParams", slog.Any("urlParams", queryParams)) + if len(queryParams) > 0 { + slog.Debug("ParamsMiddleware added urlParams", slog.Any("urlParams", queryParams)) + } next.ServeHTTP(w, r.WithContext(ctx)) }) } -- 2.49.1 From 560c539b1920e49d217e3191ea47e5723c4bf9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Tr=C3=B6ger?= Date: Sun, 18 Jan 2026 00:07:54 +0100 Subject: [PATCH 5/5] fixed minor bugs + added loggin middleware --- Backend/endpoints/time-create.go | 2 ++ Backend/go.sum | 5 ++--- Backend/main.go | 17 +++++++++++++++++ Backend/models/booking.go | 5 +++-- Backend/models/iworkday.go | 9 +++------ Backend/models/workDay.go | 2 +- 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Backend/endpoints/time-create.go b/Backend/endpoints/time-create.go index 5f64103..f368b19 100644 --- a/Backend/endpoints/time-create.go +++ b/Backend/endpoints/time-create.go @@ -7,6 +7,7 @@ import ( "errors" "log" "net/http" + "time" ) // Relevant for arduino inputs -> creates new Booking from get and put method @@ -36,6 +37,7 @@ func createBooking(w http.ResponseWriter, r *http.Request) { } booking := (*models.Booking).FromUrlParams(nil, r.URL.Query()) + booking.Timestamp = time.Now() if booking.Verify() { err := booking.Insert() if errors.Is(models.SameBookingError{}, err) { diff --git a/Backend/go.sum b/Backend/go.sum index 0c9f849..705f796 100644 --- a/Backend/go.sum +++ b/Backend/go.sum @@ -4,8 +4,6 @@ github.com/Dadido3/go-typst v0.8.0 h1:uTLYprhkrBjwsCXRRuyYUFL0fpYHa2kIYoOB/CGqVN github.com/Dadido3/go-typst v0.8.0/go.mod h1:QYis9sT70u65kn1SkFfyPRmHsPxgoxWbAixwfPReOZA= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY= -github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM= github.com/a-h/templ v0.3.960/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw= @@ -39,6 +37,8 @@ github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4 github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -78,7 +78,6 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 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/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/wlbr/feiertage v1.10.0/go.mod h1:wJOHvMa6sI5L1FkrTOX/GSoO0hpK3S2YqGLPi8Q84I0= github.com/wlbr/feiertage v1.17.0 h1:AEck/iUQu19iU0xNEoSQTeSTGXF1Ju0tbAwEi/Lmwqk= github.com/wlbr/feiertage v1.17.0/go.mod h1:TVZgmSZgGW/jSxexZ56qdlR6cDj+F/FO8bkw8U6kYxM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/Backend/main.go b/Backend/main.go index c13fcde..e0817dd 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -80,6 +80,8 @@ func main() { serverSessionMiddleware := endpoints.Session.LoadAndSave(server) + serverSessionMiddleware = loggingMiddleware(serverSessionMiddleware) + // starting the http server slog.Info("Server is running at http://localhost:8080") slog.Error("Error starting Server", "Error", http.ListenAndServe(":8080", serverSessionMiddleware)) @@ -95,3 +97,18 @@ func ParamsMiddleware(next http.HandlerFunc) http.Handler { next.ServeHTTP(w, r.WithContext(ctx)) }) } + +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + // Log the method and the requested URL + slog.Info("Started request", slog.String("Method", r.Method), slog.String("Path", r.URL.Path)) + + // Call the next handler in the chain + next.ServeHTTP(w, r) + + // Log how long it took + slog.Info("Completet Request", slog.String("Time", time.Since(start).String())) + }) +} diff --git a/Backend/models/booking.go b/Backend/models/booking.go index 55e3aca..463da92 100644 --- a/Backend/models/booking.go +++ b/Backend/models/booking.go @@ -245,12 +245,13 @@ func (b *Booking) Update(nb Booking) { func checkLastBooking(b Booking) bool { var check_in_out int - stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 ORDER BY "timestamp" DESC LIMIT 1;`)) + slog.Info("Checking with timestamp:", "timestamp", b.Timestamp.String()) + stmt, err := DB.Prepare((`SELECT check_in_out FROM "anwesenheit" WHERE "card_uid" = $1 AND "timestamp"::DATE <= $2::DATE ORDER BY "timestamp" DESC LIMIT 1;`)) if err != nil { log.Fatalf("Error preparing query: %v", err) return false } - err = stmt.QueryRow(b.CardUID).Scan(&check_in_out) + err = stmt.QueryRow(b.CardUID, b.Timestamp).Scan(&check_in_out) if err == sql.ErrNoRows { return true } diff --git a/Backend/models/iworkday.go b/Backend/models/iworkday.go index 6305842..1170aa1 100644 --- a/Backend/models/iworkday.go +++ b/Backend/models/iworkday.go @@ -49,19 +49,16 @@ func GetDays(user User, tsFrom, tsTo time.Time, orderedForward bool) []IWorkDay } for _, absentDay := range absences { - // Kurzarbeit should be integrated in workday + + // Check if there is already a day existingDay, ok := allDays[absentDay.Date().Format(time.DateOnly)] - if !ok { - allDays[absentDay.Date().Format(time.DateOnly)] = &absentDay - continue - } switch { case absentDay.AbwesenheitTyp.WorkTime < 0: if workDay, ok := allDays[absentDay.Date().Format(time.DateOnly)].(*WorkDay); ok { workDay.kurzArbeit = true workDay.kurzArbeitAbsence = absentDay } - case !existingDay.IsEmpty(): + case ok && !existingDay.IsEmpty(): allDays[absentDay.Date().Format(time.DateOnly)] = NewCompondDay(absentDay.Date(), existingDay, &absentDay) default: allDays[absentDay.Date().Format(time.DateOnly)] = &absentDay diff --git a/Backend/models/workDay.go b/Backend/models/workDay.go index 12c6669..b69a388 100644 --- a/Backend/models/workDay.go +++ b/Backend/models/workDay.go @@ -331,7 +331,7 @@ func GetWorkDays(user User, tsFrom, tsTo time.Time) []WorkDay { if len(workDay.Bookings) == 1 && workDay.Bookings[0].CounterId == 0 { workDay.Bookings = []Booking{} } - if len(workDay.Bookings) > 1 || !helper.IsWeekend(workDay.Date()) { + if len(workDay.Bookings) >= 1 || !helper.IsWeekend(workDay.Date()) { workDays = append(workDays, workDay) } } -- 2.49.1