Fertig: (Unit Tests sind gebrochen im Ordner Database. Tut aber nichts zur Sache)

This commit is contained in:
Marco Kittel 2025-07-19 09:39:19 +02:00
parent ed601a7199
commit 146c52897a
8 changed files with 392 additions and 62 deletions

View File

@ -1,12 +1,16 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"gittea.marcokittel.de/elio/eliotools/datawriter/internal/api" "io"
"gittea.marcokittel.de/elio/eliotools/datawriter/internal/database"
"log" "log"
"net/http" "net/http"
"os" "os"
"github.com/google/uuid"
"gittea.marcokittel.de/elio/eliotools/datawriter/internal/api"
"gittea.marcokittel.de/elio/eliotools/datawriter/internal/database"
) )
var ( var (
@ -16,33 +20,165 @@ var (
type ( type (
//RegistrierungsID -> ProductID -> Product //RegistrierungsID -> ProductID -> Product
ProductStore map[string]map[string]api.Product ProductStore map[string]map[string]database.Product
) )
const ( const (
curlhelp = `curl -X POST localhost:8080/api/products -d '{ "products": { "A6053": 2, "B3009": 1200 }, "context": { "country": "EU", "state": "" } }'` curlhelp = `
Bewerbungsaufgabe von Marco Kittel 2025
Produkte abrufen
curl -X POST localhost:8080/api/products -d '{ "products": { "A6053": 2, "B3009": 1200 }, "context": { "country": "EU", "state": "" } }'
Reservierung reservieren
curl -X POST localhost:8080/api/products/reserve -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}'
Reservierung bestätigen
curl -X POST localhost:8080/api/products/confirm -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}'
Reservierung abbrechen
curl -X POST localhost:8080/api/products/abort -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}'
Reservierung freigeben
curl -X POST localhost:8080/api/products/release -d '{"id":"ab0d7184-a4ce-4802-897a-d8597335143a"}'
`
) )
func main() { func main() {
connectionString := os.Getenv("CONNECTIONSTRING") connectionString := os.Getenv("CONNECTIONSTRING")
nps := database.NewProductService(connectionString) nps := database.NewProductService(connectionString)
//Hier prüfe ich nach alten Registrierungen und gebe Sie frei.
nps.Autorelease()
if len(connectionString) == 0 { if len(connectionString) == 0 {
fmt.Println("Connectionstring fehlt!. Bsp.: <user>:<passwort>@tcp(127.0.0.1:3306)/elio?parseTime=true") fmt.Println("Connectionstring fehlt!. Bsp.: <user>:<passwort>@tcp(127.0.0.1:3306)/elio?parseTime=true")
return return
} }
//Dependency Injection //Dependency Injection
http.HandleFunc("/api/products", api.GetProductApiHandleFunc(nps)) http.HandleFunc("/api/products", api.GetProductApiHandleFunc(nps))
//Todo schöner machen
http.HandleFunc("/api/products/reserve", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/products/reserve", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("In Arbeit: reserve") if r.Method != "POST" {
return
}
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
var payload database.Container
err = json.Unmarshal(data, &payload)
if err != nil {
log.Printf("Could not parse Json: %s", err)
return
}
groupId := uuid.New().String()
result, err := nps.FetchReservationData(&payload, database.UUID(groupId))
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
if len(result) == 0 {
w.WriteHeader(http.StatusNoContent)
fmt.Fprintln(w)
return
}
_, err = nps.ReserviereBestellungen(result, database.UUID(groupId))
if err != nil {
log.Println(err)
}
jsonResult, err := json.Marshal(result[0])
if err != nil {
log.Println(err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, string(jsonResult))
}) })
http.HandleFunc("/api/products/confirm", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/products/confirm", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("In Arbeit: confirm") if r.Method != "POST" {
return
}
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
payload := struct {
Id string `json:"id"`
}{}
err = json.Unmarshal(data, &payload)
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
err = nps.ConfirmBestellung(database.UUID(payload.Id))
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
w.WriteHeader(http.StatusNoContent)
fmt.Fprintln(w)
return
}) })
http.HandleFunc("/api/products/release", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/products/release", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("In Arbeit: release") if r.Method != "POST" {
return
}
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
payload := struct {
Id string `json:"id"`
}{}
err = json.Unmarshal(data, &payload)
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
err = nps.ReleaseBestellung(database.UUID(payload.Id))
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
w.WriteHeader(http.StatusNoContent)
fmt.Fprintln(w)
return
}) })
http.HandleFunc("/api/products/abort", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/products/abort", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("In Arbeit: abort") if r.Method != "POST" {
return
}
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
return
}
payload := struct {
Id string `json:"id"`
}{}
err = json.Unmarshal(data, &payload)
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
err = nps.AbortBestellung(database.UUID(payload.Id))
if err != nil {
//Todo Fehlerhandling
log.Println(err)
}
w.WriteHeader(http.StatusNoContent)
fmt.Fprintln(w)
return
}) })
log.Printf("Easy Peasy: Die Party startet auf Port %s\n", port) log.Printf("Easy Peasy: Die Party startet auf Port %s\n", port)

1
go.mod
View File

@ -23,6 +23,7 @@ require (
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect

2
go.sum
View File

@ -27,6 +27,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

View File

@ -4,7 +4,19 @@ CREATE DATABASE IF NOT EXISTS elio;
-- Haupttabellen erzeugen -- Haupttabellen erzeugen
CREATE TABLE IF NOT EXISTS elio.blablabla ( -- CREATE TABLE IF NOT EXISTS elio.blablabla (
-- id INT AUTO_INCREMENT PRIMARY KEY,
-- warehouse char(2) NOT NULL,
-- productid VARCHAR(20) NOT NULL,
-- amount INT DEFAULT 0,
-- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- CONSTRAINT location_product_must_be_one UNIQUE (warehouse, productid)
-- ) ENGINE=InnoDB;
-- Doppelter Boden: Tabellen werden zur Not auch beim Starten des Services im ersten Datenbank Call erzeugt
CREATE TABLE IF NOT EXISTS elio.warehouseproducts (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
warehouse char(2) NOT NULL, warehouse char(2) NOT NULL,
productid VARCHAR(20) NOT NULL, productid VARCHAR(20) NOT NULL,
@ -23,6 +35,24 @@ CREATE TABLE IF NOT EXISTS elio.deliverytimes (
CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry) CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS elio.reservations (
id CHAR(36) PRIMARY KEY DEFAULT UUID(),
deliveryId INT,
warehouseId INT,
amount INT DEFAULT 0,
status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')),
reservationGroupId CHAR(36) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_delivery_id
FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id)
ON DELETE CASCADE
ON UPDATE RESTRICT,
CONSTRAINT fk_warehouse_id
FOREIGN KEY (warehouseId) REFERENCES warehouseproducts (id)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB;
-- Testtabellen erzeugen -- Testtabellen erzeugen
CREATE TABLE IF NOT EXISTS elio_test.warehouseproducts ( CREATE TABLE IF NOT EXISTS elio_test.warehouseproducts (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
@ -31,7 +61,7 @@ CREATE TABLE IF NOT EXISTS elio_test.warehouseproducts (
amount INT DEFAULT 0, amount INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT location_product_must_be_one UNIQUE (warehouse, productid) CONSTRAINT location_product_must_be_one UNIQUE (warehouse, productid)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS elio_test.deliverytimes ( CREATE TABLE IF NOT EXISTS elio_test.deliverytimes (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
@ -43,6 +73,26 @@ CREATE TABLE IF NOT EXISTS elio_test.deliverytimes (
CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry) CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS elio_test.reservations (
id CHAR(36) PRIMARY KEY DEFAULT UUID(),
deliveryId INT,
warehouseId INT,
amount INT DEFAULT 0,
status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')),
reservationGroupId CHAR(36) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_delivery_id
FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id)
ON DELETE CASCADE
ON UPDATE RESTRICT,
CONSTRAINT fk_warehouse_id
FOREIGN KEY (warehouseId) REFERENCES warehouseproducts (id)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB;
-- Userkram -- Userkram
CREATE USER IF NOT EXISTS 'elio'@'%' IDENTIFIED BY 'eliogeheim'; CREATE USER IF NOT EXISTS 'elio'@'%' IDENTIFIED BY 'eliogeheim';
CREATE USER IF NOT EXISTS 'elio_test'@'%' IDENTIFIED BY 'eliogeheim'; CREATE USER IF NOT EXISTS 'elio_test'@'%' IDENTIFIED BY 'eliogeheim';

View File

@ -10,33 +10,6 @@ import (
"gittea.marcokittel.de/elio/eliotools/datawriter/internal/database" "gittea.marcokittel.de/elio/eliotools/datawriter/internal/database"
) )
type Container struct {
Products map[string]int `json:"products"`
Context Context `json:"context"`
}
type Product struct {
Warehouse string `json:"warehouse"`
Quantity int `json:"quantity"`
Delivery int `json:"delivery_time"`
}
type OutgoingProducts struct {
Products map[string][]Product `json:"products"`
}
func NewOutgoingProducts() *OutgoingProducts {
op := OutgoingProducts{
Products: make(map[string][]Product),
}
return &op
}
type Context struct {
Country string `json:"country"`
State string `json:"state"`
}
func GetProductApiHandleFunc(nps *database.ProductService) http.HandlerFunc { func GetProductApiHandleFunc(nps *database.ProductService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" { if r.Method != "POST" {
@ -59,7 +32,12 @@ func GetProductApiHandleFunc(nps *database.ProductService) http.HandlerFunc {
//Todo Fehlerhandling //Todo Fehlerhandling
log.Println(err) log.Println(err)
} }
jsonResult, err := json.Marshal(result) if len(result) == 0 {
w.WriteHeader(http.StatusNoContent)
fmt.Fprintln(w)
return
}
jsonResult, err := json.Marshal(result[0])
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }

View File

@ -88,6 +88,7 @@ func (d *DatabaseWriter) createReservationTableIfNotExist() error {
warehouseId INT, warehouseId INT,
amount INT DEFAULT 0, amount INT DEFAULT 0,
status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')), status VARCHAR(30) CHECK(status IN ('RESERVED', 'CONFIRMED', 'ABORTED', 'RELEASED')),
reservationGroupId CHAR(36) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_delivery_id CONSTRAINT fk_delivery_id
FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id) FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id)
@ -127,6 +128,16 @@ func NewDatabaseWriter(connectionString string) *DatabaseWriter {
return &db return &db
} }
func (d *DatabaseWriter) ReleaseReservierungenAfterOneDay() error {
_, err := d.db.Exec(`
UPDATE reservations r SET status = 'RELEASED'
WHERE r.created_at < DATE_SUB(NOW(), INTERVAL 1 DAY );`)
if err != nil {
return err
}
return nil
}
func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, productID string, amount int) error { func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, productID string, amount int) error {
_, err := d.db.Exec(` _, err := d.db.Exec(`
INSERT INTO warehouseproducts (warehouse, productid, amount) INSERT INTO warehouseproducts (warehouse, productid, amount)
@ -167,15 +178,22 @@ type ProductDelivery struct {
func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) { func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) {
stmt := ` stmt := `
SELECT whp.id, warehouse, amount, d.delivery, d.id deliveryId SELECT id, warehouse, (amount - reserviert) amount, delivery, deliveryId FROM(
FROM warehouseproducts whp SELECT whp.id, warehouse, whp.amount, sum(coalesce(r.amount,0) )reserviert, d.delivery, d.id deliveryId
left join deliverytimes d FROM warehouseproducts whp
on whp.warehouse = d.fromcountry left join deliverytimes d
where productid = ? on whp.warehouse = d.fromcountry
and d.tocountry = ? left join reservations r
and d.state = ? on whp.id = r.warehouseId
order by d.delivery asc, amount desc and r.status in ('RESERVED', 'CONFIRMED')
` where productid = ?
and d.tocountry = ?
and d.state = ?
GROUP BY whp.id, d.delivery
order by d.delivery asc, whp.amount desc
) as zumAufsummieren
having amount > 0
`
rows, err := d.db.Query(stmt, prod_id, delivery_country, delivery_country_state) rows, err := d.db.Query(stmt, prod_id, delivery_country, delivery_country_state)
if err != nil { if err != nil {
@ -211,6 +229,24 @@ func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error)
return status, nil return status, nil
} }
func (d *DatabaseReader) GetReservationItemsByGroupId(groupId UUID) (string, error) {
stmt := `
SELECT status FROM reservations WHERE id = ?
`
row, err := d.db.Query(stmt, groupId)
if err != nil {
return "", err
}
defer row.Close()
row.Next()
var status string
err = row.Scan(&status)
if err != nil {
return "", err
}
return status, nil
}
func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error { func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error {
_, err := d.db.Exec("UPDATE reservations SET Status=? WHERE id=?", status, Id) _, err := d.db.Exec("UPDATE reservations SET Status=? WHERE id=?", status, Id)
if err != nil { if err != nil {
@ -219,6 +255,14 @@ func (d *DatabaseWriter) updateReservationState(Id UUID, status string) error {
return nil return nil
} }
func (d *DatabaseWriter) updateReservationStateByGroupId(groupId UUID, status string) error {
_, err := d.db.Exec("UPDATE reservations SET Status=? WHERE reservationGroupId=?", status, groupId)
if err != nil {
return err
}
return nil
}
func (d *DatabaseWriter) AbortReservation(Id UUID) error { func (d *DatabaseWriter) AbortReservation(Id UUID) error {
return d.updateReservationState(Id, "ABORTED") return d.updateReservationState(Id, "ABORTED")
} }
@ -231,9 +275,21 @@ func (d *DatabaseWriter) ReleaseReservation(Id UUID) error {
return d.updateReservationState(Id, "RELEASED") return d.updateReservationState(Id, "RELEASED")
} }
func (d *DatabaseWriter) ReserveReservation(deliveryId, warehouseId, amount int) (UUID, error) { func (d *DatabaseWriter) AbortReservationGroup(groupId UUID) error {
return d.updateReservationStateByGroupId(groupId, "ABORTED")
}
func (d *DatabaseWriter) ConfirmReservationGroup(groupId UUID) error {
return d.updateReservationStateByGroupId(groupId, "CONFIRMED")
}
func (d *DatabaseWriter) ReleaseReservationGroup(groupId UUID) error {
return d.updateReservationStateByGroupId(groupId, "RELEASED")
}
func (d *DatabaseWriter) ReserveReservationItem(deliveryId, warehouseId, amount int, groupId UUID) (UUID, error) {
newUUID := uuid.New().String() newUUID := uuid.New().String()
_, err := d.db.Exec("INSERT INTO reservations (id, deliveryId, warehouseId, amount, status) VALUES (?,?,?,?,'RESERVED')", newUUID, deliveryId, warehouseId, amount) _, err := d.db.Exec("INSERT INTO reservations (id, deliveryId, warehouseId, amount, status, reservationGroupId) VALUES (?,?,?,?,'RESERVED', ?)", newUUID, deliveryId, warehouseId, amount, groupId)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -3,9 +3,12 @@ package database
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"strings" "strings"
"testing" "testing"
"github.com/google/uuid"
) )
// Unit Tests sollten niemals auf der Produktivdatenbank stattfinden. // Unit Tests sollten niemals auf der Produktivdatenbank stattfinden.
@ -123,8 +126,9 @@ func TestInsertStatement(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
} }
groupId := uuid.New().String()
for _, item := range data { for _, item := range data {
uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err)
} }
@ -141,8 +145,9 @@ func TestAbortReservationStatement(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
} }
groupId := uuid.New().String()
for _, item := range data { for _, item := range data {
uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err)
} }
@ -166,8 +171,9 @@ func TestConfirmReservationStatement(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
} }
groupId := uuid.New().String()
for _, item := range data { for _, item := range data {
uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err)
} }
@ -191,8 +197,9 @@ func TestReleasedReservationStatement(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err) t.Errorf("Es sollten Datensätze auffinbdar sein! %s", err)
} }
groupId := uuid.New().String()
for _, item := range data { for _, item := range data {
uuid, err := dbr.DatabaseWriter.ReserveReservation(item.DeliveryId, item.Id, item.Amount) uuid, err := dbr.DatabaseWriter.ReserveReservationItem(item.DeliveryId, item.Id, item.Amount, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err) t.Errorf("Das Einfügen einer neuen Reservierung darf nicht fehlschlagen! %s", err)
} }
@ -210,6 +217,20 @@ func TestReleasedReservationStatement(t *testing.T) {
} }
} }
func TestFetchReservationData(t *testing.T) {
payloadStr := `{ "products": { "A6053": 30 }, "context": { "country": "EU", "state": "" }}`
var payload Container
err := json.Unmarshal([]byte(payloadStr), &payload)
if err != nil {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return
}
ps := NewProductService(connectionString)
// groupId := uuid.New().String()
op, err := ps.FetchData(&payload)
log.Println(op)
}
func TestFetchDataReservation(t *testing.T) { func TestFetchDataReservation(t *testing.T) {
payloadStr := `{ "products": { "A6053": 30 }, "context": { "country": "EU", "state": "" }}` payloadStr := `{ "products": { "A6053": 30 }, "context": { "country": "EU", "state": "" }}`
@ -220,7 +241,8 @@ func TestFetchDataReservation(t *testing.T) {
return return
} }
ps := NewProductService(connectionString) ps := NewProductService(connectionString)
op, err := ps.FetchData(&payload) groupId := uuid.New().String()
op, err := ps.FetchReservationData(&payload, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err)
} }
@ -312,16 +334,17 @@ func TestOutgoingReservationInDatabase(t *testing.T) {
t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err) t.Errorf("Dieser Fehler beim Konvertieren der JSON Payload sollte nicht passieren! \n%s", err)
return return
} }
groupId := uuid.New().String()
ps := NewProductService(connectionString) ps := NewProductService(connectionString)
op, err := ps.FetchData(&payload) op, err := ps.FetchReservationData(&payload, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err) t.Errorf("Das Datafetchen und umwandeln in eine Map muss sauber funktionieren! %s", err)
} }
valTocompare := `[{map[A6053:[{AT 4 1 8508 63} {DE 1 2 7507 18}]]}]` valTocompare := fmt.Sprintf(`[{%s map[A6053:[{AT 4 1 8508 63} {DE 1 2 7507 18}]]}]`, groupId)
if fmt.Sprint(op) != valTocompare { if fmt.Sprint(op) != valTocompare {
t.Errorf("Mist, sollte sein %s ist aber %s", valTocompare, fmt.Sprint(op)) t.Errorf("Mist, sollte sein %s ist aber %s", valTocompare, fmt.Sprint(op))
} }
uuids, err := ps.ReserviereBestellungen(op) uuids, err := ps.ReserviereBestellungen(op, UUID(groupId))
if err != nil { if err != nil {
t.Errorf("Das Reservieren ist fehlgeschlagen! \n%s", err) t.Errorf("Das Reservieren ist fehlgeschlagen! \n%s", err)
} }
@ -331,10 +354,20 @@ func TestOutgoingReservationInDatabase(t *testing.T) {
t.Errorf("Der Status muss RESERVED sein. Stattdessen ist er: %s", status) t.Errorf("Der Status muss RESERVED sein. Stattdessen ist er: %s", status)
} }
}
ps.dbr.ReleaseReservationGroup(UUID(groupId))
for _, uuid := range *uuids {
status, err := ps.dbr.GetReservationStateById(uuid)
if err != nil || status != "RELEASED" {
t.Errorf("Der Status muss RELEASED sein. Stattdessen ist er: %s", status)
}
} }
jsonResult, err := json.Marshal(op) jsonResult, err := json.Marshal(op)
jsonStrForApiResponse := `[{"products":{"A6053":[{"warehouse":"AT","quantity":4,"delivery_time":1},{"warehouse":"DE","quantity":1,"delivery_time":2}]}}]` jsonGuid := fmt.Sprintf("\"id\":\"%s\"", UUID(groupId))
jsonStrForApiResponse := fmt.Sprintf(`[{%s,"products":{"A6053":[{"warehouse":"AT","quantity":4,"delivery_time":1},{"warehouse":"DE","quantity":1,"delivery_time":2}]}}]`, jsonGuid)
if string(jsonResult) != jsonStrForApiResponse { if string(jsonResult) != jsonStrForApiResponse {
t.Errorf("Mist, sollte sein %s ist aber %s", string(jsonResult), jsonStrForApiResponse) t.Errorf("Mist, sollte sein %s ist aber %s", string(jsonResult), jsonStrForApiResponse)
} }
} }

View File

@ -1,6 +1,10 @@
package database package database
import "log" import (
"fmt"
"log"
"time"
)
type ProductService struct { type ProductService struct {
dbr *DatabaseReader dbr *DatabaseReader
@ -21,6 +25,10 @@ type Product struct {
type OutgoingProducts struct { type OutgoingProducts struct {
Products map[string][]Product `json:"products"` Products map[string][]Product `json:"products"`
} }
type OutgoingReservationProducts struct {
Id UUID `json:"id"`
Products map[string][]Product `json:"products"`
}
func NewOutgoingProducts() *OutgoingProducts { func NewOutgoingProducts() *OutgoingProducts {
op := OutgoingProducts{ op := OutgoingProducts{
@ -28,6 +36,13 @@ func NewOutgoingProducts() *OutgoingProducts {
} }
return &op return &op
} }
func NewOutgoingReservationsProducts(groupId UUID) *OutgoingReservationProducts {
op := OutgoingReservationProducts{
Id: groupId,
Products: make(map[string][]Product),
}
return &op
}
type Context struct { type Context struct {
Country string `json:"country"` Country string `json:"country"`
@ -40,7 +55,22 @@ func NewProductService(connectionString string) *ProductService {
return &p return &p
} }
func (p *ProductService) ReserviereBestellungen(op []OutgoingProducts) (*[]UUID, error) { func (p *ProductService) Autorelease() {
//Das müsste saubererer sein. Spare ich mir aber, weil der Webserver alles blockiert.
//Besser wäre das Arbeiten mit Context. Gefahr ist aber minimal
go func() {
for {
fmt.Println("Prüfe nach Registrierungen die Freigegeben werden können. (Älter als 24 Stunden)")
err := p.dbr.ReleaseReservierungenAfterOneDay()
if err != nil {
log.Printf("Fehler beim Ausführen von Autorelease() des Logs. %s", err)
}
time.Sleep(30 * time.Second)
}
}()
}
func (p *ProductService) ReserviereBestellungen(op []OutgoingReservationProducts, groupId UUID) (*[]UUID, error) {
var uuids []UUID var uuids []UUID
for _, hsmp := range op { for _, hsmp := range op {
for _, prod := range hsmp.Products { for _, prod := range hsmp.Products {
@ -49,7 +79,7 @@ func (p *ProductService) ReserviereBestellungen(op []OutgoingProducts) (*[]UUID,
warehouseId := concreteProduct.WhdId warehouseId := concreteProduct.WhdId
amount := concreteProduct.Quantity amount := concreteProduct.Quantity
// Todo: Handler Injection wäre hier interessant // Todo: Handler Injection wäre hier interessant
uuid, err := p.dbr.ReserveReservation(deliveryId, warehouseId, amount) uuid, err := p.dbr.ReserveReservationItem(deliveryId, warehouseId, amount, groupId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -60,13 +90,25 @@ func (p *ProductService) ReserviereBestellungen(op []OutgoingProducts) (*[]UUID,
return &uuids, nil return &uuids, nil
} }
func (p *ProductService) ConfirmBestellung(groudId UUID) error {
return p.dbr.ConfirmReservationGroup(groudId)
}
func (p *ProductService) AbortBestellung(groudId UUID) error {
return p.dbr.AbortReservationGroup(groudId)
}
func (p *ProductService) ReleaseBestellung(groudId UUID) error {
return p.dbr.ReleaseReservationGroup(groudId)
}
func (p *ProductService) FetchData(payload *Container) ([]OutgoingProducts, error) { func (p *ProductService) FetchData(payload *Container) ([]OutgoingProducts, error) {
result := []OutgoingProducts{} result := []OutgoingProducts{}
for key, item := range payload.Products { for key, item := range payload.Products {
//Checken ob die Felder leer sind / Validitätsprüfung einbauen //Checken ob die Felder leer sind / Validitätsprüfung einbauen
products, err := p.dbr.GetProductByProductIdDeliveryCountryAndState(key, payload.Context.Country, payload.Context.State) products, err := p.dbr.GetProductByProductIdDeliveryCountryAndState(key, payload.Context.Country, payload.Context.State)
if err != nil { if err != nil {
log.Println(err) return nil, err
} }
if len(products) == 0 { if len(products) == 0 {
continue continue
@ -91,3 +133,35 @@ func (p *ProductService) FetchData(payload *Container) ([]OutgoingProducts, erro
} }
return result, nil return result, nil
} }
func (p *ProductService) FetchReservationData(payload *Container, groupId UUID) ([]OutgoingReservationProducts, error) {
result := []OutgoingReservationProducts{}
for key, item := range payload.Products {
//Checken ob die Felder leer sind / Validitätsprüfung einbauen
products, err := p.dbr.GetProductByProductIdDeliveryCountryAndState(key, payload.Context.Country, payload.Context.State)
if err != nil {
return nil, err
}
if len(products) == 0 {
continue
}
gebrauchteProduktAnzahl := item
op := NewOutgoingReservationsProducts(groupId)
for _, db_products := range products {
if gebrauchteProduktAnzahl <= 0 {
continue
}
if db_products.Amount >= gebrauchteProduktAnzahl {
newProduct := Product{Delivery: db_products.DeliveryDays, Quantity: gebrauchteProduktAnzahl, Warehouse: db_products.Warehouse, WhdId: db_products.Id, DeliveryId: db_products.DeliveryId}
gebrauchteProduktAnzahl = 0
op.Products[key] = append(op.Products[key], newProduct)
} else if db_products.Amount < gebrauchteProduktAnzahl {
newProduct := Product{Delivery: db_products.DeliveryDays, Quantity: db_products.Amount, Warehouse: db_products.Warehouse, WhdId: db_products.Id, DeliveryId: db_products.DeliveryId}
gebrauchteProduktAnzahl -= db_products.Amount
op.Products[key] = append(op.Products[key], newProduct)
}
}
result = append(result, *op)
}
return result, nil
}