package database import ( "context" "database/sql" "fmt" "os" "strconv" "strings" "sync" "time" _ "github.com/go-sql-driver/mysql" "gittea.marcokittel.de/elio/eliotools/eliofile" "gittea.marcokittel.de/elio/eliotools/logger" ) type DatabaseWriter struct { mu sync.Mutex log logger.Logger db *sql.DB } type DatabaseReader struct { DatabaseWriter } type UUID string func NewDatabaseReader(connectionString string) *DatabaseReader { return &DatabaseReader{*NewDatabaseWriter(connectionString)} } func (d *DatabaseWriter) connectDB(connectionString string) (*sql.DB, error) { db, err := sql.Open("mysql", connectionString) if err != nil { return nil, err } err = db.Ping() if err != nil { return nil, err } // Verbindungspool konfigurieren db.SetMaxOpenConns(40) db.SetMaxIdleConns(25) db.SetConnMaxLifetime(5 * time.Minute) return db, nil } func (d *DatabaseWriter) createWarehouseTableIfNotExist() error { _, err := d.db.Exec(` CREATE TABLE IF NOT EXISTS warehouseproducts ( 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; `) return err } func (d *DatabaseWriter) createDeliveryTableIfNotExist() error { _, err := d.db.Exec(` CREATE TABLE IF NOT EXISTS deliverytimes ( id INT AUTO_INCREMENT PRIMARY KEY, fromcountry varchar(4) NOT NULL, tocountry varchar(4) NOT NULL, state varchar(4) NULL, delivery INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT delivery_from_to_country_must_be_one UNIQUE (fromcountry, tocountry) ) ENGINE=InnoDB;`) return err } // Die Idee ist, dass ich die Reservierungen via mit Verweisen auf die Primärschlüssel // von deliverytimes und warehouseproducts verknüpfe. Ziel ist es, über den Status // das Fetch Select SQL Statement für die Abfrage der Reservierungen zu erweitern um // über die SUM Methode im SQL Query bei STATUS RESERVED, den reduzierten Mengenwert anzuzuzeigen // für das Registrierte Produkt func (d *DatabaseWriter) createReservationTableIfNotExist() error { _, err := d.db.Exec(` CREATE TABLE IF NOT EXISTS 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')), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_delivery_id FOREIGN KEY (deliveryId) REFERENCES deliverytimes (id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_warehouse_id FOREIGN KEY (warehouseId) REFERENCES warehouseproducts (id) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB;`) return err } func NewDatabaseWriter(connectionString string) *DatabaseWriter { db := DatabaseWriter{log: logger.NewMarcoLogger()} sql, err := db.connectDB(connectionString) if err != nil { fmt.Printf("Datenbank nicht gefunden. %s", err) os.Exit(1) } db.db = sql err = db.createWarehouseTableIfNotExist() if err != nil { fmt.Printf("Warenhaus-Tabellen-Erstellung fehlgeschlagen. %s", err) os.Exit(1) } err = db.createDeliveryTableIfNotExist() if err != nil { fmt.Printf("Delivery-Tabellen-Erstellung fehlgeschlagen. %s", err) os.Exit(1) } err = db.createReservationTableIfNotExist() if err != nil { fmt.Printf("Reservations-Tabellen-Erstellung fehlgeschlagen. %s", err) os.Exit(1) } return &db } func (d *DatabaseWriter) UpdateOrInsertWarehouseProduct(warehouse string, productID string, amount int) error { _, err := d.db.Exec(` INSERT INTO warehouseproducts (warehouse, productid, amount) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = VALUES(amount)`, warehouse, productID, amount) if err != nil { return fmt.Errorf("upsert failed: %w", err) } return nil } func (d *DatabaseWriter) UpdateOrInsertDelivery(fromcountry string, tocountry string, state string, delivery int) error { _, err := d.db.Exec(` INSERT INTO deliverytimes (fromcountry, tocountry, state, delivery) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE fromcountry = VALUES(fromcountry), tocountry = VALUES(tocountry), state = VALUES(state), delivery = VALUES(delivery)`, fromcountry, tocountry, state, delivery) if err != nil { return fmt.Errorf("upsert failed: %w", err) } return nil } type ProductDelivery struct { Id int Warehouse string Amount int DeliveryDays int } func (d *DatabaseReader) GetProductByProductIdDeliveryCountryAndState(prod_id, delivery_country, delivery_country_state string) ([]ProductDelivery, error) { stmt := ` SELECT whp.id, warehouse, amount, d.delivery FROM warehouseproducts whp left join deliverytimes d on whp.warehouse = d.fromcountry where productid = ? and d.tocountry = ? and d.state = ? order by d.delivery asc, amount desc ` rows, err := d.db.Query(stmt, prod_id, delivery_country, delivery_country_state) if err != nil { return nil, err } defer rows.Close() var products []ProductDelivery for rows.Next() { var pd ProductDelivery if err := rows.Scan(&pd.Id, &pd.Warehouse, &pd.Amount, &pd.DeliveryDays); err != nil { return products, err } products = append(products, pd) } return products, nil } func (d *DatabaseReader) GetReservationStateById(productId UUID) (string, error) { stmt := ` SELECT status FROM reservations WHERE id = ? ` row, err := d.db.Query(stmt, productId) 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(productId UUID, status string) error { _, err := d.db.Exec("UPDATE reservations SET Status=? WHERE id=?", status, productId) if err != nil { return err } return nil } func (d *DatabaseWriter) AbortReservation(productId UUID) error { return d.updateReservationState(productId, "ABORTED") } func (d *DatabaseWriter) ConfirmReservation(productId UUID) error { return d.updateReservationState(productId, "CONFIRMED") } func (d *DatabaseWriter) ReleaseReservation(productId UUID) error { return d.updateReservationState(productId, "RELEASED") } func (d *DatabaseWriter) ReserveReservation(deliveryId, warehouseId, amount int) error { _, err := d.db.Exec("INSERT INTO reservations (deliveryId, warehouseId, amount, status) VALUES (?,?,?,'RESERVED')") if err != nil { return err } return nil } func (db *DatabaseWriter) HandleData(ctx context.Context, data eliofile.CountryCsvData) error { // a.log.Info("HandleData") if strings.Contains(strings.Join(data.Data, ","), "product_id") || strings.Contains(strings.Join(data.Data, ","), "delivery_time") { return nil } //Todo: Csv reparieren. switch data.Type { case "stock": { menge := strings.Split(data.Data[0], ";") amount, err := strconv.Atoi(menge[1]) if err != nil { fmt.Println(err) return err } err = db.UpdateOrInsertWarehouseProduct(data.CountryID, menge[0], amount) if err != nil { return err } } case "delivery": { menge := strings.Split(data.Data[0], ";") amount, err := strconv.Atoi(menge[2]) if err != nil { fmt.Println(err) return err } err = db.UpdateOrInsertDelivery(data.CountryID, menge[0], menge[1], amount) if err != nil { return err } } } return nil }