Files
old-backend/database/device/device.go
2023-11-02 11:56:45 +01:00

344 lines
12 KiB
Go

package device
import (
"errors"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/jinzhu/gorm"
"gitlab.com/pactual1/backend/models"
"gitlab.com/pactual1/backend/services/messaging"
"gitlab.com/pactual1/backend/shared"
)
func GetDevicesForContract(contractID uint64) ([]models.Device, int, error) {
// Fetch the contract from the database
var contract models.Contract
if err := shared.GetDb().Where("id = ?", contractID).First(&contract).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("GetDevicesByContract Error: No contract found: %v", err)
return nil, http.StatusNotFound, err
} else {
log.Printf("GetDevicesByContract Error: Database error: %v", err)
return nil, http.StatusInternalServerError, err
}
}
log.Printf("This is the device IDS ID: %v", contract.DeviceIDs)
return GetDevicesByID(contract.DeviceIDs)
}
func GetDevicesByID(deviceIDs []int64) ([]models.Device, int, error) {
// Create a slice to hold the devices
var devices []models.Device
// Convert the integer array to a comma-separated string
idStrings := []string{}
for _, id := range deviceIDs {
idStrings = append(idStrings, strconv.FormatInt(id, 10))
}
idStr := strings.Join(idStrings, ",")
// Construct the SQL query manually
sqlQuery := fmt.Sprintf("SELECT * FROM devices WHERE id IN (%s)", idStr)
// Fetch devices from the database based on DeviceIDs in the contract
// Execute the query
if err := shared.GetDb().Raw(sqlQuery).Scan(&devices).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("GetDevicesByContract Error: No devices found: %v", err)
return devices, http.StatusNotFound, err
} else {
log.Printf("GetDevicesByContract Error: Database error: %v", err)
return devices, http.StatusInternalServerError, err
}
}
return devices, http.StatusOK, nil
}
func GetDevicesByImei(imeiIDs []string) ([]models.Device, int, error) {
// Create a slice to hold the devices
var devices []models.Device
// Quote each IMEI ID
for i, imei := range imeiIDs {
imeiIDs[i] = "'" + imei + "'"
}
// Join the quoted IMEI IDs into a comma-separated string
idStr := strings.Join(imeiIDs, ",")
// Construct the SQL query manually
sqlQuery := fmt.Sprintf("SELECT * FROM devices WHERE imei IN (%s)", idStr)
// Fetch devices from the database based on DeviceIDs in the contract
// Execute the query
if err := shared.GetDb().Raw(sqlQuery).Scan(&devices).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("GetDevicesByContract Error: No devices found: %v", err)
return devices, http.StatusNotFound, err
} else {
log.Printf("GetDevicesByContract Error: Database error: %v", err)
return devices, http.StatusInternalServerError, err
}
}
return devices, http.StatusOK, nil
}
func GetDeviceInfoForContract(deviceID uint64, contract models.Contract) (models.GeoJSONFeatureCollection, int, error) {
var deviceInfos []models.DeviceInfo
// Create a GeoJSON feature collection
var featureCollection models.GeoJSONFeatureCollection
featureCollection.Type = "FeatureCollection"
featureCollection.Features = []models.GeoJSONFeature{}
// Modify your query to include filtering based on the contract's creation date
query := shared.GetDb().Where("device_id = ?", deviceID).Where("created_at >= ? AND created_at <= ?", contract.StartTime, contract.EndTime)
if err := query.Find(&deviceInfos).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("GetDeviceData Error: No device info found: %v", err)
return featureCollection, http.StatusNotFound, err
} else {
log.Printf("GetDeviceData Error: Database error: %v", err)
return featureCollection, http.StatusNotFound, err
}
}
deviceInfosResponse := models.ConvertDeviceInfoToResponse(deviceInfos)
// Loop through each deviceInfo to create GeoJSON features
for _, info := range deviceInfosResponse {
info.RawJSON = ""
feature := models.GeoJSONFeature{
Type: "Feature",
DeviceInfoResponse: &info,
Geometry: models.GeoJSONGeometry{
Type: "Point",
Coordinates: []float64{info.Lon, info.Lat},
},
Properties: map[string]interface{}{},
}
featureCollection.Features = append(featureCollection.Features, feature)
}
return featureCollection, 0, nil
}
func SaveDeviceInfoToDB(deviceInfo models.DeviceInfo, rawData []byte) (models.DeviceInfo, models.Device, error) {
deviceInfo.RawJSON = string(rawData)
deviceInfo.X = deviceInfo.AccInfo.X
deviceInfo.Y = deviceInfo.AccInfo.Y
deviceInfo.Z = deviceInfo.AccInfo.Z
var device models.Device
if err := shared.GetDb().Unscoped().Where("device_id = ?", deviceInfo.ExternalDeviceID).First(&device).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
newDevice := models.Device{
IMEI: deviceInfo.IMEI,
IMSI: deviceInfo.IMSI,
DeviceID: deviceInfo.ExternalDeviceID,
DeviceConfiguration: string(rawData),
}
if err := shared.GetDb().Create(&newDevice).Error; err != nil {
return deviceInfo, device, fmt.Errorf("CREATE -Device DB Error: %v", err)
}
deviceInfo.DeviceID = newDevice.ID
} else {
return deviceInfo, device, fmt.Errorf("CREATE -Device DB Error: %v", err)
}
} else {
if device.DeletedAt.Valid {
if err := shared.GetDb().Exec("UPDATE devices SET deleted_at = NULL, company_id = NULL WHERE id = ?", device.ID).Error; err != nil {
return deviceInfo, device, fmt.Errorf("UNDELETE -Device DB Error: %v", err)
}
}
deviceInfo.DeviceID = device.ID
}
if err := shared.GetDb().Create(&deviceInfo).Error; err != nil {
return deviceInfo, device, fmt.Errorf("SaveDeviceInfo CREATE -DeviceInfo DB Error: %v", err)
}
go func(deviceInfo models.DeviceInfo) {
var contract models.Contract
if err := shared.GetDb().Where("device_ids @> ARRAY[?]::integer[]", deviceInfo.DeviceID).First(&contract).Error; err != nil {
log.Printf("could not find associated contract: %v", err)
}
messagingChannel := messaging.GetMessagingChannel()
if deviceInfo.Temperature > contract.MaxTemp || deviceInfo.Temperature < contract.MinTemp {
notification := models.Notification{
Title: "Temperature Alert",
NotificationType: "Temperature",
Text: fmt.Sprintf("Device %s has a temperature outside the permitted range.", deviceInfo.ExternalDeviceID),
CompanyID: int(contract.BuyerID),
}
sellerNotification := models.Notification{
Title: "Temperature Alert",
NotificationType: "Temperature",
Text: fmt.Sprintf("Device %s has a temperature outside the permitted range.", deviceInfo.ExternalDeviceID),
CompanyID: int(contract.SellerID),
}
messagingChannel <- notification
messagingChannel <- sellerNotification
}
}(deviceInfo)
return deviceInfo, device, nil
}
func CountDeviceInfoByCompany(companyID uint) (int64, error) {
var contracts []models.Contract
var allDeviceIDs []int64
var count int64
// Find all contracts where the company is either a buyer or a seller
if err := shared.GetDb().Where("buyer_id = ? OR seller_id = ?", companyID, companyID).Find(&contracts).Error; err != nil {
log.Printf("CountDeviceInfoByCompany Error: Database error: %v", err)
return 0, err
}
// Aggregate all DeviceIDs from these contracts
for _, contract := range contracts {
allDeviceIDs = append(allDeviceIDs, contract.DeviceIDs...)
}
// Count DeviceInfo entries related to these DeviceIDs
if err := shared.GetDb().Model(&models.DeviceInfo{}).Where("device_id IN (?)", allDeviceIDs).Count(&count).Error; err != nil {
log.Printf("CountDeviceInfoByCompany Error: Database error: %v", err)
return 0, err
}
return count, nil
}
func CountDeviceBreachedAndNormalDevicesByCompany(companyID uint, startTime, endTime time.Time) (int64, int64, map[string]map[string]int64, error) {
var contracts []models.Contract
var allDeviceIDs []int64
var inRangeCount, outOfRangeCount int64
monthlyCounts := make(map[string]map[string]int64)
// Fetch all contracts related to the company
if err := shared.GetDb().Where("buyer_id = ? OR seller_id = ?", companyID, companyID).Find(&contracts).Error; err != nil {
log.Printf("CountDeviceInfoByCompany Error: Database error: %v", err)
return 0, 0, nil, err
}
// Aggregate all DeviceIDs
for _, contract := range contracts {
allDeviceIDs = append(allDeviceIDs, contract.DeviceIDs...)
}
// Iterate through each month within the specified date range
for dt := startTime; dt.Before(endTime); dt = dt.AddDate(0, 1, 0) {
monthEnd := dt.AddDate(0, 1, 0)
if monthEnd.After(endTime) {
monthEnd = endTime
}
startUnix := dt.Unix()
endUnix := monthEnd.Unix()
// Initialize monthlyCounts for the current month
monthKey := dt.Format("2006-01")
monthlyCounts[monthKey] = map[string]int64{"inRange": 0, "outOfRange": 0}
// Count DeviceInfo entries with temperatures in range and out of range for the current month
for _, contract := range contracts {
var inRange, outOfRange int64
err := shared.GetDb().Model(&models.DeviceInfo{}).
Where("device_id IN (?) AND temperature >= ? AND temperature <= ? AND timestamp >= ? AND timestamp <= ?",
allDeviceIDs, contract.MinTemp, contract.MaxTemp, startUnix, endUnix).
Count(&inRange).Error
if err != nil {
return 0, 0, nil, err
}
err = shared.GetDb().Model(&models.DeviceInfo{}).
Where("device_id IN (?) AND (temperature < ? OR temperature > ?) AND timestamp >= ? AND timestamp <= ?",
allDeviceIDs, contract.MinTemp, contract.MaxTemp, startUnix, endUnix).
Count(&outOfRange).Error
if err != nil {
return 0, 0, nil, err
}
// Update the counts for the current month
monthlyCounts[monthKey]["inRange"] += inRange
monthlyCounts[monthKey]["outOfRange"] += outOfRange
// Update the total counts
inRangeCount += inRange
outOfRangeCount += outOfRange
}
}
return inRangeCount, outOfRangeCount, monthlyCounts, nil
}
type ContractLocationMatch struct {
ContractID uint
DeviceInfoID uint
}
func FetchMatchingContractsAndDeviceInfo(companyID uint64, startTime, endTime time.Time) ([]ContractLocationMatch, error) {
var contracts []models.Contract
var results []ContractLocationMatch
registeredDevices := make(map[uint]bool) // Map to keep track of registered devices
//Fetch contracts
err := shared.GetDb().
Where("start_time >= ? AND end_time <= ? AND (buyer_id = ? OR seller_id = ?) ", startTime, endTime, companyID, companyID).
Find(&contracts).Error
if err != nil {
return nil, err
}
//Loop through each contract to find matching DeviceInfo
for _, contract := range contracts {
var deviceInfos []models.DeviceInfo
deviceIDStr := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(contract.DeviceIDs)), ","), "[]")
queryString := fmt.Sprintf("device_id IN (%s) AND created_at >= ? AND created_at <= ?", deviceIDStr)
err := shared.GetDb().
Where(queryString, contract.StartTime, contract.EndTime).
Find(&deviceInfos).Error
if err != nil {
return nil, err
}
// Compare locations and created_at
for _, deviceInfo := range deviceInfos {
// Continue to the next iteration if this device has already been registered
if registeredDevices[deviceInfo.DeviceID] {
continue
}
if shared.DistanceKm(contract.StartLat, contract.StartLon, deviceInfo.Lat, deviceInfo.Lon) <= 1 ||
shared.DistanceKm(contract.EndLat, contract.EndLon, deviceInfo.Lat, deviceInfo.Lon) <= 1 {
results = append(results, ContractLocationMatch{
ContractID: contract.ID,
DeviceInfoID: deviceInfo.ID,
})
registeredDevices[deviceInfo.DeviceID] = true // Mark this device as registered
}
}
}
return results, nil
}