diff --git a/.vscode/launch.json b/.vscode/launch.json index b3cd30f..ca9fbfc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,21 +1,19 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - - - { - "name": "Launch Package", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceFolder}/Backend", - "dlvFlags": ["--check-go-version=false"], - "env": { - "DEBUG": "true" - } - }, - ] + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Backend", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/Backend", + "dlvFlags": ["--check-go-version=false"], + "env": { + "DEBUG": "true" + } + } + ] } diff --git a/Backend/main.go b/Backend/main.go index d35d350..5f4eb42 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -21,27 +21,35 @@ func main() { } defer models.DB.Close() + // handles the different http routes http.HandleFunc("/time/new", timeCreateHandler) http.HandleFunc("/time", timeHandler) + http.HandleFunc("/logout", logoutHandler) + // starting the http server fmt.Printf("Server is running at http://localhost:8000 exposed to port %s\n", getEnv("EXPOSED_PORT", "8000")) log.Fatal(http.ListenAndServe(":8080", nil)) } +// Relevant for arduino inputs -> creates new Booking from get and put method +// GET only for demo purpose func timeCreateHandler(w http.ResponseWriter, r *http.Request) { setCors(w) + // switch with request methods switch r.Method { case "PUT": createBooking(w, r) case "GET": createBooking(w, r) case "OPTIONS": + // just support options header for non GET Requests from SWAGGER w.WriteHeader(http.StatusOK) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } +// Frontendrelevant backend functionality -> not used by the arduino devices func timeHandler(w http.ResponseWriter, r *http.Request) { setCors(w) switch r.Method { @@ -50,14 +58,49 @@ func timeHandler(w http.ResponseWriter, r *http.Request) { case "PUT": updateBooking(w, r) case "OPTIONS": + // just support options header for non GET Requests from SWAGGER w.WriteHeader(http.StatusOK) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } +} + +func logoutHandler(w http.ResponseWriter, r *http.Request) { + setCors(w) + switch r.Method { + case "GET": + autoLogout(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func autoLogout(w http.ResponseWriter, r *http.Request) { + users, err := (*models.User).GetAll(nil) + var logged_out_users []models.User + if err != nil { + fmt.Printf("Error getting user list %v\n", err) + } + for _, user := range users { + if user.CheckAnwesenheit() { + err = user.Logout() + if err != nil { + fmt.Printf("Error logging out user %v\n", err) + } + logged_out_users = append(logged_out_users, user) + } + + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(logged_out_users) } +// Creates a booking from the http query params -> no body needed +// after that entry wi'll be written to database and the booking is returned as json func createBooking(w http.ResponseWriter, r *http.Request) { + booking := (*models.Booking).FromUrlParams(nil, r.URL.Query()) if booking.Verify() { err := booking.Insert() @@ -77,13 +120,14 @@ func createBooking(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) } +// Returns bookings from DB with similar card uid -> checks for card uid in http query params func getBookings(w http.ResponseWriter, r *http.Request) { - card_id := r.URL.Query().Get("card_uid") - if card_id == "" { + card_uid := r.URL.Query().Get("card_uid") + if card_uid == "" { http.Error(w, "Missing cardID query parameter", http.StatusBadRequest) return } - bookings, err := (*models.Booking).GetBookingsByCardID(nil, card_id) + bookings, err := (*models.Booking).GetBookingsByCardID(nil, card_uid) if err != nil { log.Println("Error getting bookings: ", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) @@ -93,6 +137,7 @@ func getBookings(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(bookings) } +// Updates a booking form the given json body func updateBooking(w http.ResponseWriter, r *http.Request) { _booking_id := r.URL.Query().Get("counter_id") if _booking_id == "" { @@ -130,8 +175,12 @@ func updateBooking(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(_booking) } -// func getBooking(w http.ResponseWriter, r *http.Request) - +// Returns env with default fallback value. +// +// Params: +// +// key - enviroment var name +// fallback - default value func getEnv(key, fallback string) string { if value, ok := os.LookupEnv(key); ok { return value @@ -139,6 +188,8 @@ func getEnv(key, fallback string) string { return fallback } +// setting cors, important for later frontend use +// in DEBUG == "true" everything is set to "*" so that no cors errors will be happen func setCors(w http.ResponseWriter) { if os.Getenv("DEBUG") == "true" { w.Header().Set("Access-Control-Allow-Origin", "*") diff --git a/Backend/models/booking.go b/Backend/models/booking.go index af20db1..644f30c 100644 --- a/Backend/models/booking.go +++ b/Backend/models/booking.go @@ -25,9 +25,9 @@ type Booking struct { var DB *sql.DB -func (b Booking) New(card_id 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{ - CardUID: card_id, + CardUID: card_uid, GeraetID: geraet_id, CheckInOut: check_in_out, } @@ -89,6 +89,7 @@ func (b *Booking) GetBookingsByCardID(card_id string) ([]Booking, error) { if err != nil { return nil, err } + defer qStr.Close() var bookings []Booking rows, err := qStr.Query(card_id) if err == sql.ErrNoRows { diff --git a/Backend/models/user.go b/Backend/models/user.go new file mode 100644 index 0000000..6e48ac3 --- /dev/null +++ b/Backend/models/user.go @@ -0,0 +1,66 @@ +package models + +import ( + "fmt" +) + +type User struct { + CardUID string + Name string + Vorname string + HauptbeschaeftigungsOrt int8 +} + +func (u *User) GetAll() ([]User, error) { + qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname, hauptbeschaeftigung_ort FROM 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.HauptbeschaeftigungsOrt); err != nil { + return users, nil + } + users = append(users, user) + } + if err = rows.Err(); err != nil { + return users, nil + } + return users, nil +} + +// Returns true if there is a booking 1 for today -> meaning the user is at work +// Returns false if there is no booking today or the user is already booked out of the system +func (u *User) CheckAnwesenheit() bool { + qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp" >= now()::date + interval '1h' ORDER BY "timestamp" DESC`)) + if err != nil { + fmt.Printf("Error preparing query statement %v\n", err) + return false + } + defer qStr.Close() + var check_in_out int + err = qStr.QueryRow(u.CardUID).Scan(&check_in_out) + if err != nil { + return false + } + return check_in_out == 1 +} + +// Creates a new booking for the user -> check_in_out will be 255 for automatic check out +func (u *User) Logout() error { + booking := (*Booking).New(nil, u.CardUID, 0, 255) + err := booking.Insert() + if err != nil { + fmt.Printf("Error inserting booking %v\n", err) + return err + } + return nil +} diff --git a/db.sql b/db.sql index 9d159af..32637f6 100644 --- a/db.sql +++ b/db.sql @@ -1,30 +1,4 @@ --- @block create table -CREATE TABLE zeiten ( - id SERIAL PRIMARY KEY, - logged_time TIMESTAMP DEFAULT NOW(), - card_id VARCHAR, - reader_id VARCHAR, - booking_type INTEGER -); --- @block insert data -INSERT INTO zeiten (card_id, reader_id, booking_type) -VALUES ('test_card', 'test_reader', '2') -RETURNING id, - logged_time; --- @block select -SELECT * -FROM anwesenheit; --- @block select last entry from card id -SELECT * -FROM "zeiten" -WHERE "card_id" = 'test_card' - AND "logged_time" >= now()::date + interval '1h' -ORDER BY "logged_time" DESC -LIMIT 1; --- @block delete table -DROP TABLE IF EXISTS zeiten; -- @block create table anwesenheit -DROP TABLE IF EXISTS "public"."anwesenheit"; CREATE TABLE "public"."anwesenheit" ( "counter_id" SERIAL PRIMARY KEY, "timestamp" timestamp(6) DEFAULT CURRENT_TIMESTAMP, @@ -34,3 +8,51 @@ CREATE TABLE "public"."anwesenheit" ( ); COMMENT ON COLUMN "public"."anwesenheit"."check_in_out" IS '1=Check In 2=Check Out 255=Automatic Check Out'; COMMENT ON COLUMN "public"."anwesenheit"."geraet_id" IS 'ID des Lesegerätes'; +-- @block create table personaldaten +CREATE TABLE "public"."personal_daten" ( + "personal_nummer" SERIAL PRIMARY KEY, + "akiv_beschaeftig" bool, + "vorname" varchar COLLATE "pg_catalog"."default", + "nachname" varchar COLLATE "pg_catalog"."default", + "geburtsdatum" date, + "plz" varchar COLLATE "pg_catalog"."default", + "adresse" varchar COLLATE "pg_catalog"."default", + "geschlecht" numeric, + "card_uid" varchar(255) COLLATE "pg_catalog"."default", + "hauptbeschaeftigung_ort" int2 +); +COMMENT ON COLUMN "public"."personal_daten"."akiv_beschaeftig" IS 'derzeit aktiv beschaeftigt : 1'; +COMMENT ON COLUMN "public"."personal_daten"."geschlecht" IS 'w:1 m:2 div:3 kA:null '; +COMMENT ON COLUMN "public"."personal_daten"."card_uid" IS 'RFID-Karten-UID'; +COMMENT ON COLUMN "public"."personal_daten"."hauptbeschaeftigung_ort" IS 'Chemnitz:1 Sayda:2'; +-- @block drop tables +DROP TABLE IF EXISTS "public"."anwesenheit"; +DROP TABLE IF EXISTS "public"."personal_daten"; +-- @block insert into personal_daten +INSERT INTO personal_daten ( + personal_nummer, + akiv_beschaeftig, + vorname, + nachname, + geburtsdatum, + plz, + adresse, + geschlecht, + card_uid, + hauptbeschaeftigung_ort + ) +VALUES ( + 123, + true, + 'Max', + 'Mustermann', + now(), + '00815', + 'Musterstrasse', + 1, + 'test_card', + '1' + ); +-- @block select +SELECT * +FROM personal_daten;