package dbManager import ( "errors" "github.com/prometheus/client_golang/prometheus" "github.com/Cyclops-Labs/cyclops-4-hpc.git/services/customerdb/models" l "gitlab.com/cyclops-utilities/logging" "gorm.io/driver/postgres" "gorm.io/gorm" ) const ( statusDuplicated = iota statusFail statusMissing statusOK ) // DbParameter is the struct defined to group and contain all the methods // that interact with the database. // On it there is the following parameters: // - connStr: strings with the connection information to the database // - Db: a gorm.DB pointer to the db to invoke all the db methods type DbParameter struct { connStr string Db *gorm.DB Metrics map[string]*prometheus.GaugeVec } // New is the function to create the struct DbParameter. // Parameters: // - dbConn: strings with the connection information to the database // - tables: array of interfaces that will contains the models to migrate // to the database on initialization // Returns: // - DbParameter: struct to interact with dbManager functionalities func New(dbConn string, tables ...interface{}) *DbParameter { l.Trace.Printf("[DB] Gerenating new DBParameter.\n") var ( dp DbParameter err error ) dp.connStr = dbConn dp.Db, err = gorm.Open(postgres.Open(dbConn), &gorm.Config{}) if err != nil { l.Error.Printf("[DB] Error opening connection. Error: %v\n", err) panic(err) } l.Trace.Printf("[DB] Migrating tables.\n") //Database migration, it handles everything dp.Db.AutoMigrate(tables...) //l.Trace.Printf("[DB] Generating hypertables.\n") // Hypertables creation for timescaledb in case of needed //dp.Db.Exec("SELECT create_hypertable('" + dp.Db.NewScope(&models.TABLE).TableName() + "', 'TIMESCALE-ROW-INDEX');") return &dp } // ListResellers function extracts from the db all the resellers in the system. // It only list the resellers, not their linked customers neither the linked // products to each customer. // Returns: // - r: slice of the reseller model with all the resellers in the system. func (d *DbParameter) ListResellers() (r []*models.Reseller, e error) { l.Trace.Printf("[DB] Attempting to fetch reseller list now.\n") if e = d.Db.Find(&r).Error; e != nil { l.Warning.Printf("[DB] Error in DB operation. Error: %v\n", e) } l.Trace.Printf("[DB] Found [ %d ] resellers in the db.\n", len(r)) return } // ListCustomers function extracts from the db all the customers in the system. // It only list the customerss, not their linked products. // Returns: // - c: slice of the customers model with all the customers in the system. func (d *DbParameter) ListCustomers() (c []*models.Customer, e error) { l.Trace.Printf("[DB] Attempting to fetch customers list now.\n") if e = d.Db.Find(&c).Error; e != nil { l.Warning.Printf("[DB] Error in DB operation. Error: %v\n", e) } l.Trace.Printf("[DB] Found [ %d ] customers in the db.\n", len(c)) return } // ListProducts function extracts from the db all the products in the system. // Returns: // - p: slice of the products model with all the products in the system. func (d *DbParameter) ListProducts() (p []*models.Product, e error) { l.Trace.Printf("[DB] Attempting to fetch products list now.\n") if e = d.Db.Find(&p).Error; e != nil { l.Warning.Printf("[DB] Error in DB operation. Error: %v\n", e) } l.Trace.Printf("[DB] Found [ %d ] products in the db.\n", len(p)) return } // GetReseller function extracts from the db the information that the system // has about the specified reseller. // Internally this function makes use of GetCustomers() to fill the information // about all the customers linked to the specified reseller. // Parameters: // - id: string containing the id of the reseller to be gotten. // - host: string containing the basepath of the server for filling out the APILink // field of the linked customers and products. // Returns: // - A reference to the reseller's model containing all the information in the // system linked to the specified reseller. func (d *DbParameter) GetReseller(id string, host string) (*models.Reseller, error) { l.Trace.Printf("[DB] Attempting to get reseller [ %v ] now.\n", id) var r models.Reseller e := d.Db.Where(&models.Reseller{ResellerID: id}).First(&r).Error if errors.Is(e, gorm.ErrRecordNotFound) { l.Warning.Printf("[DB] Unable to fetch existing record for reseller [ %v ], check with administrator.\n", id) } else { l.Info.Printf("[DB] Found existing record for reseller [ %v ] successfully.\n", r.Name) r.Customers = d.GetCustomers(id, host) } return &r, e } // GetCustomer function extracts from the db the information that the system // has about the specified customer. // Internally this function makes use of GetProducts() to fill the information // about all the products linked to the specified customer. // Parameters: // - id: string containing the id of the customer to be gotten. // - host: string containing the basepath of the server for filling out the APILink // field of the linked products. // Returns: // - A reference to the customer's model containing all the information in the // system linked to the specified customer. func (d *DbParameter) GetCustomer(id string, host string) (*models.Customer, error) { l.Trace.Printf("[DB] Attempting to get customer [ %v ] now.\n", id) var c models.Customer e := d.Db.Where(&models.Customer{CustomerID: id}).First(&c).Error if errors.Is(e, gorm.ErrRecordNotFound) { l.Warning.Printf("[DB] Unable to fetch existing record for customer [ %v ], check with administrator.\n", id) } else { l.Info.Printf("[DB] Found existing record for customer [ %v ] successfully.\n", c.Name) c.Products = d.GetProducts(id, host) } return &c, e } // GetProduct function extracts from the db the information that the system // has about the specified product. // Parameters: // - id: string containing the id of the product to be gotten. // Returns: // - A reference to the product's model containing all the information in the // system linked to the specified product. func (d *DbParameter) GetProduct(id string) (*models.Product, error) { l.Trace.Printf("[DB] Attempting to get product [ %v ] now.\n", id) var p models.Product e := d.Db.Where(&models.Product{ProductID: id}).First(&p).Error if errors.Is(e, gorm.ErrRecordNotFound) { l.Warning.Printf("[DB] Unable to fetch existing record for product [ %v ], check with administrator.\n", id) } else { l.Info.Printf("[DB] Found existing record for product [ %v ] successfully.\n", p.Name) } return &p, e } // GetCustomers function extracts from the db the information that the system // has about all the customers linked to the specified reseller id. // Internally this function makes use of GetProducts() to fill the information // about all the products linked to each customer. // Parameters: // - id: string containing the id of the customer to be gotten. // - host: string containing the basepath of the server for filling out the APILink // field of the customers and the linked products. // Returns: // - A slice of the customer's model containing all the information in the // system linked to the specified reseller id. func (d *DbParameter) GetCustomers(id string, host string) (c []*models.Customer) { l.Trace.Printf("[DB] Attempting to fetch customers for reseller [ %v ] now.\n", id) var custs, customers []models.Customer if e := d.Db.Where(&models.Customer{ResellerID: id}).Find(&customers).Error; e != nil { l.Warning.Printf("[DB] Error in DB operation while retrieving linked custommers. Error: %v\n", e) } for _, customer := range customers { customer.Products = d.GetProducts(customer.CustomerID, host) customer.APILink = host + "/customer/" + customer.CustomerID custs = append(custs, customer) } //This is the only way to avoid the issue with the unproper assigning of //&Elemnt when using range over and array for id := range custs { c = append(c, &custs[id]) } l.Trace.Printf("[DB] Found [ %d ] customers for reseller [ %v ].\n", len(c), id) return } // GetProducts function extracts from the db the information that the system // has about all the products linked to the specified customer id. // Parameters: // - id: string containing the id of the customer to be gotten. // - host: string containing the basepath of the server for filling out the APILink // field of products. // Returns: // - A slice of the product's model containing all the information in the // system linked to the specified customer id. func (d *DbParameter) GetProducts(id string, host string) (p []*models.Product) { l.Trace.Printf("[DB] Attempting to fetch products for customer [ %v ] now.\n", id) var products []models.Product if e := d.Db.Where(&models.Product{CustomerID: id}).Find(&products).Error; e != nil { l.Warning.Printf("[DB] Error in DB operation while retrieving linked products. Error: %v\n", e) } for i := range products { product := products[i] product.APILink = host + "/product/" + product.ProductID p = append(p, &product) } l.Trace.Printf("[DB] Found [ %d ] products for customer [ %v ].\n", len(p), id) return } // AddReseller function inserts the reseller and its linked customers and products // into the system. // Since the system is designed to not raised a problem when it fails to add new // items in the db, the method constains a basic feedback control to know if // there was a problem when inserting the reseller or any of its linked // components in the db. // Parameters: // - r: reseller's model containing the information to be imported in the db. // Returns: // - rs: integer with the state of adding the reseller itself. // - cs: integer with the state of adding the linked customers. // - ps: integer with the state of adding the linked products. func (d *DbParameter) AddReseller(r *models.Reseller) (id string, rs int, cs int, ps int) { l.Trace.Printf("[DB] Attempting to add reseller [ %v ] now.\n", r.ResellerID) var r0 models.Reseller if e := d.Db.Where(r).First(&r0).Error; errors.Is(e, gorm.ErrRecordNotFound) { if e := d.Db.Create(r); e.Error == nil { l.Info.Printf("[DB] Inserted new record for reseller [ %v ] successfully.\n", r.Name) d.Metrics["count"].With(prometheus.Labels{"type": "Resellers added"}).Inc() rs, cs, ps = statusOK, statusOK, statusOK id = (*e.Statement.Model.(*models.Reseller)).ResellerID for _, c := range r.Customers { c.ResellerID = id _, a, b := d.AddCustomer(c) if a != statusOK { cs = statusFail } if b != statusOK { ps = statusFail } } } else { l.Warning.Printf("[DB] Unable to insert the record for reseller [ %v ], check with administrator.\n", r.Name) rs = statusFail } } else { l.Warning.Printf("[DB] Record for reseller [ %v ] already exists, check with administrator.\n", r.Name) rs = statusDuplicated } return } // AddCustomer function inserts the customer and its linked products into the system. // Since the system is designed to not raised a problem when it fails to add new // items in the db, the method constains a basic feedback control to know if // there was a problem when inserting the customer or any of its linked // components in the db. // Parameters: // - c: customer's model containing the information to be imported in the db. // Returns: // - cs: integer with the state of adding the customer itself. // - ps: integer with the state of adding the linked products. func (d *DbParameter) AddCustomer(c *models.Customer) (id string, cs int, ps int) { l.Trace.Printf("[DB] Attempting to add customer [ %v ] now.\n", c.CustomerID) var c0 models.Customer if e := d.Db.Where(c).First(&c0).Error; errors.Is(e, gorm.ErrRecordNotFound) { if e := d.Db.Create(c); e.Error == nil { l.Info.Printf("[DB] Inserted new record for customer [ %v ] successfully.\n", c.Name) d.Metrics["count"].With(prometheus.Labels{"type": "Customers added"}).Inc() cs, ps = statusOK, statusOK id = (*e.Statement.Model.(*models.Customer)).CustomerID for _, p := range c.Products { p.CustomerID = id _, a := d.AddProduct(p) if a != statusOK { ps = statusFail } } } else { l.Warning.Printf("[DB] Unable to insert the record for customer [ %v ], check with administrator.\n", c.Name) cs = statusFail } } else { l.Warning.Printf("[DB] Record for customer [ %v ] already exists, check with administrator.\n", c.Name) cs = statusDuplicated } return } // AddProduct function inserts the product into the system. // Since the system is designed to not raised a problem when it fails to add new // items in the db, the method constains a basic feedback control to know if // there was a problem when inserting the product in the db. // Parameters: // - p: product's model containing the information to be imported in the db. // Returns: // - ps: integer with the state of adding the product. func (d *DbParameter) AddProduct(p *models.Product) (id string, ps int) { l.Trace.Printf("[DB] Attempting to add product [ %v ] now.\n", p.ProductID) var p0 models.Product if e := d.Db.Where(p).First(&p0).Error; errors.Is(e, gorm.ErrRecordNotFound) { if e := d.Db.Create(p); e.Error == nil { l.Info.Printf("[DB] Inserted new record for prolduct [ %v ] successfully.\n", p.Name) id = (*e.Statement.Model.(*models.Product)).ProductID d.Metrics["count"].With(prometheus.Labels{"type": "Products added"}).Inc() ps = statusOK } else { l.Warning.Printf("[DB] Unable to insert the record for product [ %v ], check with administrator.\n", p.Name) ps = statusFail } } else { l.Warning.Printf("[DB] Record for product [ %v ] already exists, check with administrator.\n", p.Name) ps = statusDuplicated } return } // UpdateReseller function inserts the updated reseller information and its // linked customers and products into the system, creating new ones in case of // no previous version found in the database. // Since the system is designed to not raised a problem when it fails to add new // items in the db, the method constains a basic feedback control to know if // there was a problem when inserting the updated reseller or any of its linked // components in the db. // Parameters: // - r: reseller's model containing the information to be imported in the db. // Returns: // - rs: integer with the state of updating the reseller itself. // - cs: integer with the state of updating the linked customers. // - ps: integer with the state of updating the linked products. func (d *DbParameter) UpdateReseller(r *models.Reseller) (rs int, cs int, ps int) { l.Trace.Printf("[DB] Attempting to update reseller [ %v ] now.\n", r.ResellerID) var r0 models.Reseller if e := d.Db.Where(models.Reseller{ResellerID: r.ResellerID}).First(&r0).Error; !errors.Is(e, gorm.ErrRecordNotFound) { if e := d.Db.Model(&r0).Updates(*r).Error; e == nil { l.Info.Printf("[DB] Updated record for reseller [ %v ] successfully.\n", r.Name) d.Metrics["count"].With(prometheus.Labels{"type": "Resellers updated"}).Inc() rs, cs, ps = statusOK, statusOK, statusOK for _, c := range r.Customers { c.ResellerID = r.ResellerID if a, _ := d.UpdateCustomer(c); a != statusOK { l.Trace.Printf("[DB] Attempt to update customer [ %v ] failed, considering it as new customer to be added.\n", c.CustomerID) _, b, d := d.AddCustomer(c) if b != statusOK { l.Trace.Printf("[DB] Attempt to add customer [ %v ] failed.\n", c.CustomerID) cs = statusFail } if d != statusOK { ps = statusFail } } } } else { l.Warning.Printf("[DB] Unable to update record for reseller [ %v ], check with administrator.\n", r.Name) rs = statusFail } } else { l.Warning.Printf("[DB] Record for reseller [ %v ] not found, check with administrator.\n", r.Name) rs = statusMissing } return } // UpdateCustomer function inserts the updated customer information and its // linked products into the system, creating new ones in case of no previous // version found in the database. // Since the system is designed to not raised a problem when it fails to add new // items in the db, the method constains a basic feedback control to know if // there was a problem when inserting the updated customer or any of its linked // components in the db. // Parameters: // - c: customer's model containing the information to be imported in the db. // Returns: // - cs: integer with the state of updating the customer itself. // - ps: integer with the state of updating the linked products. func (d *DbParameter) UpdateCustomer(c *models.Customer) (cs int, ps int) { l.Trace.Printf("[DB] Attempting to update customer [ %v ] now.\n", c.CustomerID) var c0 models.Customer if e := d.Db.Where(&models.Customer{CustomerID: c.CustomerID}).First(&c0).Error; !errors.Is(e, gorm.ErrRecordNotFound) { if e := d.Db.Model(&c0).Updates(*c).Error; e == nil { l.Info.Printf("[DB] Updated record for customer [ %v ] successfully.\n", c.Name) d.Metrics["count"].With(prometheus.Labels{"type": "Customers updated"}).Inc() cs, ps = statusOK, statusOK for _, p := range c.Products { p.CustomerID = c.CustomerID if d.UpdateProduct(p) != statusOK { l.Trace.Printf("[DB] Attempt to update product [ %v ] failed, considering it as new product to be added.\n", p.ProductID) _, a := d.AddProduct(p) if a != statusOK { l.Trace.Printf("[DB] Attempt to add product [ %v ] failed.\n", p.ProductID) cs = statusFail } } } } else { l.Warning.Printf("[DB] Unable to update record for customer [ %v ], check with administrator.\n", c.Name) cs = statusFail } } else { l.Warning.Printf("[DB] Record for customer [ %v ] not found, check with administrator.\n", c.Name) cs = statusMissing } return } // UpdateProduct function inserts the updated product information in the system. // Since the system is designed to not raised a problem when it fails to add new // items in the db, the method constains a basic feedback control to know if // there was a problem when inserting the updated product in the db. // Parameters: // - p: product's model containing the information to be imported in the db. // Returns: // - ps: integer with the state of updating the product. func (d *DbParameter) UpdateProduct(p *models.Product) (ps int) { l.Trace.Printf("[DB] Attempting to update product [ %v ] now.\n", p.ProductID) var p0 models.Product if e := d.Db.Where(&models.Product{ProductID: p.ProductID}).First(&p0).Error; !errors.Is(e, gorm.ErrRecordNotFound) { if e := d.Db.Model(&p0).Updates(*p).Error; e == nil { l.Info.Printf("[DB] Updated record for product [ %v ] successfully.\n", p.Name) d.Metrics["count"].With(prometheus.Labels{"type": "Products updated"}).Inc() ps = statusOK } else { l.Warning.Printf("[DB] Unable to update record for product [ %v ], check with administrator.\n", p.Name) ps = statusFail } } else { l.Warning.Printf("[DB] Record for product [ %v ] not found, check with administrator.\n", p.Name) ps = statusMissing } return }