Files
old-backend/database/device/device.go

328 lines
11 KiB
Go
Raw Normal View History

2023-09-22 11:38:32 +02:00
package device
import (
"errors"
"fmt"
"log"
"math"
2023-09-22 11:38:32 +02:00
"net/http"
"strconv"
"strings"
"time"
2023-09-22 11:38:32 +02:00
"github.com/jinzhu/gorm"
"gitlab.com/pactual1/backend/models"
2023-10-16 19:43:35 +02:00
"gitlab.com/pactual1/backend/services/messaging"
2023-09-22 11:38:32 +02:00
"gitlab.com/pactual1/backend/shared"
)
2023-10-16 05:44:09 +02:00
func GetDevicesForContract(contractID uint64) ([]models.Device, int, error) {
2023-09-22 11:38:32 +02:00
// 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)
2023-10-16 05:44:09 +02:00
return nil, http.StatusNotFound, err
2023-09-22 11:38:32 +02:00
} else {
log.Printf("GetDevicesByContract Error: Database error: %v", err)
2023-10-16 05:44:09 +02:00
return nil, http.StatusInternalServerError, err
2023-09-22 11:38:32 +02:00
}
}
log.Printf("This is the device IDS ID: %v", contract.DeviceIDs)
2023-10-16 05:44:09 +02:00
return GetDevicesByID(contract.DeviceIDs)
}
2023-09-22 11:38:32 +02:00
2023-10-16 05:44:09 +02:00
func GetDevicesByID(deviceIDs []int64) ([]models.Device, int, error) {
// Create a slice to hold the devices
var devices []models.Device
2023-09-22 11:38:32 +02:00
// Convert the integer array to a comma-separated string
idStrings := []string{}
2023-10-16 05:44:09 +02:00
for _, id := range deviceIDs {
2023-09-22 11:38:32 +02:00
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)
2023-10-16 05:44:09 +02:00
return devices, http.StatusInternalServerError, err
2023-09-22 11:38:32 +02:00
}
}
2023-10-16 05:44:09 +02:00
return devices, http.StatusOK, nil
2023-09-22 11:38:32 +02:00
}
2023-10-16 05:44:09 +02:00
func GetDeviceInfoForContract(deviceID uint64, contract models.Contract) (models.GeoJSONFeatureCollection, int, error) {
2023-09-22 11:38:32 +02:00
var deviceInfos []models.DeviceInfo
2023-10-16 05:44:09 +02:00
// Create a GeoJSON feature collection
var featureCollection models.GeoJSONFeatureCollection
featureCollection.Type = "FeatureCollection"
featureCollection.Features = []models.GeoJSONFeature{}
2023-09-22 11:38:32 +02:00
2023-10-16 05:44:09 +02:00
// 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)
2023-09-22 11:38:32 +02:00
2023-10-16 05:44:09 +02:00
if err := query.Find(&deviceInfos).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Printf("GetDeviceData Error: No device info found: %v", err)
2023-09-22 11:38:32 +02:00
return featureCollection, http.StatusNotFound, err
2023-10-16 05:44:09 +02:00
} else {
log.Printf("GetDeviceData Error: Database error: %v", err)
2023-09-22 11:38:32 +02:00
return featureCollection, http.StatusNotFound, err
2023-10-16 05:44:09 +02:00
}
}
2023-09-22 11:38:32 +02:00
2023-10-06 10:47:26 +02:00
deviceInfosResponse := models.ConvertDeviceInfoToResponse(deviceInfos)
2023-09-22 11:38:32 +02:00
2023-10-16 05:44:09 +02:00
// 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
}
2023-10-16 19:43:35 +02:00
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
}
const oneKmInDegrees = 0.009 // Approximately 1 km in degrees for lat/lon
func isWithinOneKm(lat1, lon1, lat2, lon2 float64) bool {
return math.Abs(lat1-lat2) <= oneKmInDegrees && math.Abs(lon1-lon2) <= oneKmInDegrees
}
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 isWithinOneKm(contract.StartLat, contract.StartLon, deviceInfo.Lat, deviceInfo.Lon) ||
isWithinOneKm(contract.EndLat, contract.EndLon, deviceInfo.Lat, deviceInfo.Lon) {
results = append(results, ContractLocationMatch{
ContractID: contract.ID,
DeviceInfoID: deviceInfo.ID,
})
registeredDevices[deviceInfo.DeviceID] = true // Mark this device as registered
}
}
}
return results, nil
}