dev/actions feature: overtime #27

Merged
tom_trgr merged 53 commits from dev/actions into main 2025-09-04 00:58:25 +02:00
14 changed files with 126 additions and 36 deletions
Showing only changes of commit b614049d03 - Show all commits

1
.gitignore vendored
View File

@@ -37,3 +37,4 @@ DB/pg_data
node_modules node_modules
atlas.hcl atlas.hcl
.scannerwork

View File

@@ -7,39 +7,17 @@ import (
"fmt" "fmt"
) )
func OpenDatabase() (*sql.DB, error) { func OpenDatabase() (models.IDatabase, error) {
dbHost := helper.GetEnv("POSTGRES_HOST", "localhost") dbHost := helper.GetEnv("POSTGRES_HOST", "localhost")
dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung") dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung")
dbUser := helper.GetEnv("POSTGRES_API_USER", "api_nutzer") dbUser := helper.GetEnv("POSTGRES_API_USER", "api_nutzer")
dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password") dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password")
connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=Europe/Berlin", dbUser, dbPassword, dbHost, dbName) connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=Europe/Berlin", dbUser, dbPassword, dbHost, dbName)
return sql.Open("postgres", connStr) db, err := sql.Open("postgres", connStr)
}
func GetBookingsByCardID(db *sql.DB, card_id string) ([]models.Booking, error) {
qStr, err := db.Prepare((`SELECT * FROM anwesenheit WHERE card_id = $1`))
if err != nil { if err != nil {
return nil, err return nil, err
} }
var bookings []models.Booking defer db.Close()
rows, err := qStr.Query(card_id) return db, err
if err == sql.ErrNoRows {
return bookings, err
}
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var booking models.Booking
if err := rows.Scan(&booking.CounterId, &booking.Timestamp, &booking.CardUID, &booking.GeraetID, &booking.CheckInOut); err != nil {
return bookings, err
}
bookings = append(bookings, booking)
}
if err = rows.Err(); err != nil {
return bookings, err
}
return bookings, nil
} }

View File

@@ -33,7 +33,7 @@ func submitReport(w http.ResponseWriter, r *http.Request) {
userPN, _ := strconv.Atoi(r.FormValue("user")) userPN, _ := strconv.Atoi(r.FormValue("user"))
_weekTs := r.FormValue("week") _weekTs := r.FormValue("week")
weekTs, err := time.Parse(time.DateOnly, _weekTs) weekTs, err := time.Parse(time.DateOnly, _weekTs)
user, err := (*models.User).GetByPersonalNummer(nil, userPN) user, err := models.GetUserByPersonalNr(userPN)
workWeek := (*models.WorkWeek).GetWeek(nil, user, weekTs, false) workWeek := (*models.WorkWeek).GetWeek(nil, user, weekTs, false)
if err != nil { if err != nil {

View File

@@ -178,7 +178,7 @@ func getBookingsAPI(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := (*models.User).GetByPersonalNummer(nil, user_pn) user, err := models.GetUserByPersonalNr(user_pn)
if err != nil { if err != nil {
log.Println("No user found with the given personal number!") log.Println("No user found with the given personal number!")
http.Error(w, "No user found", http.StatusNotFound) http.Error(w, "No user found", http.StatusNotFound)

View File

@@ -53,7 +53,7 @@ func loginUser(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := (*models.User).GetByPersonalNummer(nil, personal_nummer) user, err := models.GetUserByPersonalNr(personal_nummer)
if err != nil { if err != nil {
log.Println("No user found under this personal number!") log.Println("No user found under this personal number!")
http.Error(w, "No user found!", http.StatusNotFound) http.Error(w, "No user found!", http.StatusNotFound)

View File

@@ -21,7 +21,7 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
showUserPage(w, r, http.StatusBadRequest) showUserPage(w, r, http.StatusBadRequest)
return return
} }
user, err := (*models.User).GetByPersonalNummer(nil, Session.GetInt(r.Context(), "user")) user, err := models.GetUserByPersonalNr(Session.GetInt(r.Context(), "user"))
if err != nil { if err != nil {
log.Println("Error getting user!", err) log.Println("Error getting user!", err)
showUserPage(w, r, http.StatusBadRequest) showUserPage(w, r, http.StatusBadRequest)

View File

@@ -35,7 +35,6 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer models.DB.Close()
fs := http.FileServer(http.Dir("./static")) fs := http.FileServer(http.Dir("./static"))
endpoints.CreateSessionManager(24 * time.Hour) endpoints.CreateSessionManager(24 * time.Hour)

View File

@@ -30,7 +30,12 @@ type Booking struct {
CounterId int `json:"counter_id"` CounterId int `json:"counter_id"`
} }
var DB *sql.DB type IDatabase interface {
Prepare(query string) (*sql.Stmt, error)
Exec(query string, args ...any) (sql.Result, error)
}
var DB IDatabase
func (b *Booking) New(card_uid string, geraet_id int16, check_in_out int16) Booking { func (b *Booking) New(card_uid string, geraet_id int16, check_in_out int16) Booking {
return Booking{ return Booking{

38
Backend/models/db_test.go Normal file
View File

@@ -0,0 +1,38 @@
package models_test
import (
"arbeitszeitmessung/models"
"database/sql"
"testing"
_ "github.com/lib/pq"
)
type DBFixture struct {
Database models.IDatabase
TX *sql.Tx
}
func SetupDBFixture(t *testing.T) *DBFixture {
t.Helper()
db, err := sql.Open("postgres", "postgres://postgres:password@localhost:5433/arbeitszeitmessung?sslmode=disable")
if err != nil {
t.Fatalf("failed to connect to database: %v", err)
}
tx, err := db.Begin()
if err != nil {
t.Fatalf("Failed to start transaction: %v", err)
}
t.Cleanup(func() {
tx.Rollback()
db.Close()
})
return &DBFixture{
Database: tx,
TX: tx,
}
}

View File

@@ -24,13 +24,13 @@ func (u *User) GetUserFromSession(Session *scs.SessionManager, ctx context.Conte
var user User var user User
var err error var err error
if helper.GetEnv("GO_ENV", "production") == "debug" { if helper.GetEnv("GO_ENV", "production") == "debug" {
user, err = (*User).GetByPersonalNummer(nil, 123) user, err = GetUserByPersonalNr(123)
} else { } else {
if !Session.Exists(ctx, "user") { if !Session.Exists(ctx, "user") {
log.Println("No user in session storage!") log.Println("No user in session storage!")
return user, errors.New("No user in session storage!") return user, errors.New("No user in session storage!")
} }
user, err = (*User).GetByPersonalNummer(nil, Session.GetInt(ctx, "user")) user, err = GetUserByPersonalNr(Session.GetInt(ctx, "user"))
} }
if err != nil { if err != nil {
log.Println("Cannot get user from session!") log.Println("Cannot get user from session!")
@@ -95,7 +95,7 @@ func (u *User) CheckOut() error {
return nil return nil
} }
func (u *User) GetByPersonalNummer(personalNummer int) (User, error) { func GetUserByPersonalNr(personalNummer int) (User, error) {
var user User var user User
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE personal_nummer = $1;`)) qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, arbeitszeit_per_tag FROM s_personal_daten WHERE personal_nummer = $1;`))

View File

@@ -0,0 +1,56 @@
package models_test
import (
"arbeitszeitmessung/models"
"database/sql"
"testing"
)
var testUser models.User = models.User{Vorname: "Kim", Name: "Mustermensch", PersonalNummer: 456, CardUID: "aaaa-aaaa", ArbeitszeitPerTag: 8}
func SetupUserFixture(t *testing.T, db models.IDatabase) {
t.Helper()
db.Exec(`INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES
(456, 't', 'Kim', 'Mustermensch', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'aaaa-aaaa', 1, 8, '07:00:00', '20:00:00', 0);`)
}
func TestGetUserByPersonalNr(t *testing.T) {
tc := SetupDBFixture(t)
SetupUserFixture(t, tc.Database)
models.DB = tc.Database
user, err := models.GetUserByPersonalNr(testUser.PersonalNummer)
if err != nil {
t.Fatal(err)
}
if user != testUser {
t.Error("Retrieved user not the same as testUser!")
}
_, err = models.GetUserByPersonalNr(000)
if err != sql.ErrNoRows {
t.Error("Wrong error handling, when retrieving wrong personalnummer")
}
}
func TestCheckAnwesenheit(t *testing.T) {
tc := SetupDBFixture(t)
models.DB = tc.Database
SetupUserFixture(t, tc.Database)
var actual bool
if actual = testUser.CheckAnwesenheit(); actual != false {
t.Errorf("Checkabwesenheit with no booking should be false but is %t", actual)
}
tc.Database.Exec("INSERT INTO anwesenheit (timestamp, card_uid, check_in_out, geraet_id) VALUES (NOW() - INTERVAL '2 hour', 'aaaa-aaaa', 1, 1);")
if actual = testUser.CheckAnwesenheit(); actual != true {
t.Errorf("Checkabwesenheit with 'kommen' booking should be true but is %t", actual)
}
tc.Database.Exec("INSERT INTO anwesenheit (timestamp, card_uid, check_in_out, geraet_id) VALUES (NOW() - INTERVAL '1 hour', 'aaaa-aaaa', 2, 1);")
if actual = testUser.CheckAnwesenheit(); actual != false {
t.Errorf("Checkabwesenheit with 'gehen' booking should be false but is %t", actual)
}
}

View File

@@ -1,5 +1,5 @@
INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES INSERT INTO "s_personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES
(123, 't', 'Max', 'Mustermann', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'acde-edca', 1, 7.5, '07:00:00', '20:00:00', 0); (123, 't', 'Kim', 'Mustermensch', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'aaaa-aaaa', 1, 8, '07:00:00', '20:00:00', 0);
INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES INSERT INTO "user_password" ("personal_nummer", "pass_hash") VALUES
(123, crypt('max_pass', gen_salt('bf'))); (123, crypt('max_pass', gen_salt('bf')));

View File

@@ -0,0 +1,13 @@
name: arbeitszeitmessung-test
services:
db:
image: postgres:16
restart: unless-stopped
env_file:
- .env.test
environment:
PGDATA: /var/lib/postgresql/data/pg_data
volumes:
- ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d
ports:
- 5433:5432