2026-07-07 10:29:28 +08:00
|
|
|
package database
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
"gorm.io/gorm/clause"
|
|
|
|
|
|
2026-07-07 14:40:09 +08:00
|
|
|
"onixbyte.com/pipely/internal/model"
|
2026-07-07 10:29:28 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// -- Admin Users --
|
|
|
|
|
|
|
|
|
|
func CreateAdminUser(db *gorm.DB, u *model.AdminUser) error {
|
|
|
|
|
return db.Create(u).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetAdminUserByUsername(db *gorm.DB, username string) (*model.AdminUser, error) {
|
|
|
|
|
var u model.AdminUser
|
|
|
|
|
err := db.Where("username = ?", username).First(&u).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &u, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ListAdminUsers(db *gorm.DB) ([]model.AdminUser, error) {
|
|
|
|
|
var users []model.AdminUser
|
|
|
|
|
err := db.Order("created_at").Find(&users).Error
|
|
|
|
|
return users, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DeleteAdminUser(db *gorm.DB, id int64) error {
|
|
|
|
|
return db.Delete(&model.AdminUser{}, id).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func UpdateAdminPassword(db *gorm.DB, id int64, passwordHash string) error {
|
|
|
|
|
return db.Model(&model.AdminUser{}).Where("id = ?", id).
|
|
|
|
|
Update("password_hash", passwordHash).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Devices --
|
|
|
|
|
|
|
|
|
|
func UpsertDevice(db *gorm.DB, d *model.Device) error {
|
|
|
|
|
return db.Clauses(clause.OnConflict{
|
|
|
|
|
Columns: []clause.Column{{Name: "device_id"}},
|
|
|
|
|
DoUpdates: clause.AssignmentColumns([]string{"ip_address", "hostname", "os", "current_version", "status", "group_name", "last_heartbeat", "updated_at"}),
|
|
|
|
|
}).Create(d).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func UpdateDeviceHeartbeat(db *gorm.DB, deviceID, version, status string) error {
|
|
|
|
|
return db.Model(&model.Device{}).Where("device_id = ?", deviceID).
|
|
|
|
|
Updates(map[string]interface{}{
|
|
|
|
|
"current_version": version,
|
|
|
|
|
"status": status,
|
|
|
|
|
"last_heartbeat": time.Now(),
|
|
|
|
|
}).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetDeviceByID(db *gorm.DB, deviceID string) (*model.Device, error) {
|
|
|
|
|
var d model.Device
|
|
|
|
|
err := db.Where("device_id = ?", deviceID).First(&d).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ListDevices(db *gorm.DB, status, group string, page, pageSize int) ([]model.Device, int64, error) {
|
|
|
|
|
var devices []model.Device
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
q := db.Model(&model.Device{})
|
|
|
|
|
if status != "" {
|
|
|
|
|
q = q.Where("status = ?", status)
|
|
|
|
|
}
|
|
|
|
|
if group != "" {
|
|
|
|
|
q = q.Where("group_name = ?", group)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := q.Count(&total).Error; err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := q.Order("last_heartbeat DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&devices).Error
|
|
|
|
|
return devices, total, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetOfflineDevices(db *gorm.DB, threshold time.Duration) ([]model.Device, error) {
|
|
|
|
|
var devices []model.Device
|
|
|
|
|
err := db.Where("last_heartbeat < ? AND status = ?", time.Now().Add(-threshold), model.DeviceStatusOnline).
|
|
|
|
|
Find(&devices).Error
|
|
|
|
|
return devices, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MarkDevicesOffline(db *gorm.DB, deviceIDs []string) error {
|
|
|
|
|
return db.Model(&model.Device{}).Where("device_id IN ?", deviceIDs).
|
|
|
|
|
Update("status", model.DeviceStatusOffline).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Artefacts --
|
|
|
|
|
|
|
|
|
|
func CreateArtefact(db *gorm.DB, a *model.Artefact) error {
|
|
|
|
|
return db.Create(a).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetArtefactByID(db *gorm.DB, id int64) (*model.Artefact, error) {
|
|
|
|
|
var a model.Artefact
|
|
|
|
|
err := db.First(&a, id).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &a, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetArtefactByVersion(db *gorm.DB, version string) (*model.Artefact, error) {
|
|
|
|
|
var a model.Artefact
|
|
|
|
|
err := db.Where("version = ?", version).Order("created_at DESC").First(&a).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &a, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ListArtefacts(db *gorm.DB, page, pageSize int) ([]model.Artefact, int64, error) {
|
|
|
|
|
var artefacts []model.Artefact
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
db.Model(&model.Artefact{}).Count(&total)
|
|
|
|
|
err := db.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&artefacts).Error
|
|
|
|
|
return artefacts, total, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Deployments --
|
|
|
|
|
|
|
|
|
|
func CreateDeployment(db *gorm.DB, d *model.Deployment) error {
|
|
|
|
|
return db.Create(d).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetDeploymentByID(db *gorm.DB, id int64) (*model.Deployment, error) {
|
|
|
|
|
var d model.Deployment
|
|
|
|
|
err := db.Preload("Artefact").First(&d, id).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &d, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ListDeployments(db *gorm.DB, status string, page, pageSize int) ([]model.Deployment, int64, error) {
|
|
|
|
|
var deployments []model.Deployment
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
q := db.Model(&model.Deployment{})
|
|
|
|
|
if status != "" {
|
|
|
|
|
q = q.Where("status = ?", status)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := q.Count(&total).Error; err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := q.Preload("Artefact").Order("created_at DESC").
|
|
|
|
|
Offset((page - 1) * pageSize).Limit(pageSize).Find(&deployments).Error
|
|
|
|
|
return deployments, total, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func UpdateDeploymentStatus(db *gorm.DB, id int64, status string) error {
|
|
|
|
|
return db.Model(&model.Deployment{}).Where("id = ?", id).Update("status", status).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Deployment Devices --
|
|
|
|
|
|
|
|
|
|
func CreateDeploymentDevices(db *gorm.DB, deploymentID int64, deviceIDs []string) error {
|
|
|
|
|
devices := make([]model.DeploymentDevice, len(deviceIDs))
|
|
|
|
|
for i, did := range deviceIDs {
|
|
|
|
|
devices[i] = model.DeploymentDevice{DeploymentID: deploymentID, DeviceID: did, Status: model.DeployDeviceStatusPending}
|
|
|
|
|
}
|
|
|
|
|
return db.Clauses(clause.OnConflict{DoNothing: true}).Create(&devices).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetActiveDeploymentForDevice(db *gorm.DB, deviceID string) (*model.Deployment, *model.Artefact, error) {
|
|
|
|
|
var dd model.DeploymentDevice
|
|
|
|
|
err := db.Where("device_id = ? AND status = ?", deviceID, model.DeployDeviceStatusPending).
|
|
|
|
|
Preload("Deployment.Artefact").First(&dd).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var d model.Deployment
|
|
|
|
|
db.Preload("Artefact").First(&d, dd.DeploymentID)
|
|
|
|
|
if d.Status != model.DeploymentStatusActive {
|
|
|
|
|
return nil, nil, gorm.ErrRecordNotFound
|
|
|
|
|
}
|
|
|
|
|
return &d, d.Artefact, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func UpdateDeploymentDeviceStatus(db *gorm.DB, deploymentID int64, deviceID, status, errMsg string) error {
|
|
|
|
|
updates := map[string]interface{}{"status": status, "error": errMsg}
|
|
|
|
|
now := time.Now()
|
|
|
|
|
if status == model.DeployDeviceStatusDownloaded {
|
|
|
|
|
updates["downloaded_at"] = &now
|
|
|
|
|
}
|
|
|
|
|
if status == model.DeployDeviceStatusCompleted || status == model.DeployDeviceStatusFailed {
|
|
|
|
|
updates["completed_at"] = &now
|
|
|
|
|
}
|
|
|
|
|
return db.Model(&model.DeploymentDevice{}).
|
|
|
|
|
Where("deployment_id = ? AND device_id = ?", deploymentID, deviceID).
|
|
|
|
|
Updates(updates).Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetDeploymentDeviceStats(db *gorm.DB, deploymentID int64) (map[string]int64, error) {
|
|
|
|
|
type row struct {
|
|
|
|
|
Status string
|
|
|
|
|
Count int64
|
|
|
|
|
}
|
|
|
|
|
var rows []row
|
|
|
|
|
if err := db.Model(&model.DeploymentDevice{}).
|
|
|
|
|
Select("status, COUNT(*) as count").
|
|
|
|
|
Where("deployment_id = ?", deploymentID).
|
|
|
|
|
Group("status").Scan(&rows).Error; err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
stats := map[string]int64{}
|
|
|
|
|
for _, r := range rows {
|
|
|
|
|
stats[r.Status] = r.Count
|
|
|
|
|
}
|
|
|
|
|
return stats, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetDeploymentDevices(db *gorm.DB, deploymentID int64, status string, page, pageSize int) ([]model.DeploymentDevice, int64, error) {
|
|
|
|
|
var devices []model.DeploymentDevice
|
|
|
|
|
var total int64
|
|
|
|
|
|
|
|
|
|
q := db.Model(&model.DeploymentDevice{}).Where("deployment_id = ?", deploymentID)
|
|
|
|
|
if status != "" {
|
|
|
|
|
q = q.Where("status = ?", status)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
q.Count(&total)
|
|
|
|
|
err := q.Order("created_at").Offset((page - 1) * pageSize).Limit(pageSize).Find(&devices).Error
|
|
|
|
|
return devices, total, err
|
|
|
|
|
}
|