Session 4 complete - RedFlag update management platform
🚩 Private development - version retention only ✅ Complete web dashboard (React + TypeScript + TailwindCSS) ✅ Production-ready server backend (Go + Gin + PostgreSQL) ✅ Linux agent with APT + Docker scanning + local CLI tools ✅ JWT authentication and REST API ✅ Update discovery and approval workflow 🚧 Status: Alpha software - active development 📦 Purpose: Version retention during development ⚠️ Not for public use or deployment
This commit is contained in:
141
aggregator-server/internal/database/queries/updates.go
Normal file
141
aggregator-server/internal/database/queries/updates.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aggregator-project/aggregator-server/internal/models"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type UpdateQueries struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewUpdateQueries(db *sqlx.DB) *UpdateQueries {
|
||||
return &UpdateQueries{db: db}
|
||||
}
|
||||
|
||||
// UpsertUpdate inserts or updates an update package
|
||||
func (q *UpdateQueries) UpsertUpdate(update *models.UpdatePackage) error {
|
||||
query := `
|
||||
INSERT INTO update_packages (
|
||||
id, agent_id, package_type, package_name, package_description,
|
||||
current_version, available_version, severity, cve_list, kb_id,
|
||||
repository_source, size_bytes, status, metadata
|
||||
) VALUES (
|
||||
:id, :agent_id, :package_type, :package_name, :package_description,
|
||||
:current_version, :available_version, :severity, :cve_list, :kb_id,
|
||||
:repository_source, :size_bytes, :status, :metadata
|
||||
)
|
||||
ON CONFLICT (agent_id, package_type, package_name, available_version)
|
||||
DO UPDATE SET
|
||||
package_description = EXCLUDED.package_description,
|
||||
current_version = EXCLUDED.current_version,
|
||||
severity = EXCLUDED.severity,
|
||||
cve_list = EXCLUDED.cve_list,
|
||||
kb_id = EXCLUDED.kb_id,
|
||||
repository_source = EXCLUDED.repository_source,
|
||||
size_bytes = EXCLUDED.size_bytes,
|
||||
metadata = EXCLUDED.metadata,
|
||||
discovered_at = NOW()
|
||||
`
|
||||
_, err := q.db.NamedExec(query, update)
|
||||
return err
|
||||
}
|
||||
|
||||
// ListUpdates retrieves updates with filtering
|
||||
func (q *UpdateQueries) ListUpdates(filters *models.UpdateFilters) ([]models.UpdatePackage, int, error) {
|
||||
var updates []models.UpdatePackage
|
||||
whereClause := []string{"1=1"}
|
||||
args := []interface{}{}
|
||||
argIdx := 1
|
||||
|
||||
if filters.AgentID != nil {
|
||||
whereClause = append(whereClause, fmt.Sprintf("agent_id = $%d", argIdx))
|
||||
args = append(args, *filters.AgentID)
|
||||
argIdx++
|
||||
}
|
||||
if filters.Status != "" {
|
||||
whereClause = append(whereClause, fmt.Sprintf("status = $%d", argIdx))
|
||||
args = append(args, filters.Status)
|
||||
argIdx++
|
||||
}
|
||||
if filters.Severity != "" {
|
||||
whereClause = append(whereClause, fmt.Sprintf("severity = $%d", argIdx))
|
||||
args = append(args, filters.Severity)
|
||||
argIdx++
|
||||
}
|
||||
if filters.PackageType != "" {
|
||||
whereClause = append(whereClause, fmt.Sprintf("package_type = $%d", argIdx))
|
||||
args = append(args, filters.PackageType)
|
||||
argIdx++
|
||||
}
|
||||
|
||||
// Get total count
|
||||
countQuery := "SELECT COUNT(*) FROM update_packages WHERE " + strings.Join(whereClause, " AND ")
|
||||
var total int
|
||||
err := q.db.Get(&total, countQuery, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated results
|
||||
query := fmt.Sprintf(`
|
||||
SELECT * FROM update_packages
|
||||
WHERE %s
|
||||
ORDER BY discovered_at DESC
|
||||
LIMIT $%d OFFSET $%d
|
||||
`, strings.Join(whereClause, " AND "), argIdx, argIdx+1)
|
||||
|
||||
limit := filters.PageSize
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
offset := (filters.Page - 1) * limit
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
args = append(args, limit, offset)
|
||||
err = q.db.Select(&updates, query, args...)
|
||||
return updates, total, err
|
||||
}
|
||||
|
||||
// GetUpdateByID retrieves a single update by ID
|
||||
func (q *UpdateQueries) GetUpdateByID(id uuid.UUID) (*models.UpdatePackage, error) {
|
||||
var update models.UpdatePackage
|
||||
query := `SELECT * FROM update_packages WHERE id = $1`
|
||||
err := q.db.Get(&update, query, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &update, nil
|
||||
}
|
||||
|
||||
// ApproveUpdate marks an update as approved
|
||||
func (q *UpdateQueries) ApproveUpdate(id uuid.UUID, approvedBy string) error {
|
||||
query := `
|
||||
UPDATE update_packages
|
||||
SET status = 'approved', approved_by = $1, approved_at = NOW()
|
||||
WHERE id = $2 AND status = 'pending'
|
||||
`
|
||||
_, err := q.db.Exec(query, approvedBy, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateUpdateLog inserts an update log entry
|
||||
func (q *UpdateQueries) CreateUpdateLog(log *models.UpdateLog) error {
|
||||
query := `
|
||||
INSERT INTO update_logs (
|
||||
id, agent_id, update_package_id, action, result,
|
||||
stdout, stderr, exit_code, duration_seconds
|
||||
) VALUES (
|
||||
:id, :agent_id, :update_package_id, :action, :result,
|
||||
:stdout, :stderr, :exit_code, :duration_seconds
|
||||
)
|
||||
`
|
||||
_, err := q.db.NamedExec(query, log)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user