feat: basic firmware registry
This commit is contained in:
190
internal/service/firmware.go
Normal file
190
internal/service/firmware.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"spore-registry/internal/models"
|
||||
"spore-registry/internal/repository"
|
||||
"spore-registry/internal/storage"
|
||||
)
|
||||
|
||||
// FirmwareService handles business logic for firmware operations
|
||||
type FirmwareService struct {
|
||||
repo *repository.FirmwareRepository
|
||||
storage *storage.FileStorage
|
||||
}
|
||||
|
||||
// NewFirmwareService creates a new firmware service
|
||||
func NewFirmwareService(repo *repository.FirmwareRepository, storage *storage.FileStorage) *FirmwareService {
|
||||
return &FirmwareService{
|
||||
repo: repo,
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
// UploadFirmware uploads firmware binary and metadata
|
||||
func (s *FirmwareService) UploadFirmware(metadata models.FirmwareMetadata, data io.Reader) (*models.UploadResponse, error) {
|
||||
// Validate required fields
|
||||
if metadata.Name == "" || metadata.Version == "" {
|
||||
return nil, fmt.Errorf("name and version are required")
|
||||
}
|
||||
|
||||
// Store firmware binary
|
||||
filePath, size, err := s.storage.StoreFirmwareBinary(metadata.Name, metadata.Version, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to store firmware binary: %w", err)
|
||||
}
|
||||
|
||||
// Store metadata in database
|
||||
if err := s.repo.StoreFirmwareMetadata(metadata, size); err != nil {
|
||||
// Try to clean up the file if database operation fails
|
||||
os.Remove(filePath)
|
||||
return nil, fmt.Errorf("failed to store firmware metadata: %w", err)
|
||||
}
|
||||
|
||||
return &models.UploadResponse{
|
||||
Success: true,
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListFirmware retrieves firmware records with optional filtering
|
||||
func (s *FirmwareService) ListFirmware(name, version string) ([]models.GroupedFirmware, error) {
|
||||
records, err := s.repo.GetFirmwareRecords(name, version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve firmware records: %w", err)
|
||||
}
|
||||
|
||||
grouped := s.groupFirmwareByName(records)
|
||||
return grouped, nil
|
||||
}
|
||||
|
||||
// UpdateFirmwareMetadata updates firmware metadata
|
||||
func (s *FirmwareService) UpdateFirmwareMetadata(name, version string, metadata models.FirmwareMetadata) (*models.UploadResponse, error) {
|
||||
// Validate that the name and version match the URL
|
||||
if metadata.Name != name || metadata.Version != version {
|
||||
return nil, fmt.Errorf("name and version in URL must match metadata")
|
||||
}
|
||||
|
||||
// Check if firmware exists
|
||||
records, err := s.repo.GetFirmwareRecords(name, version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if firmware exists: %w", err)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return nil, fmt.Errorf("firmware not found")
|
||||
}
|
||||
|
||||
// Get existing firmware size (keep the same size since we're not updating the binary)
|
||||
existingRecord := records[0]
|
||||
size := existingRecord.Size
|
||||
|
||||
// Update metadata in database
|
||||
if err := s.repo.StoreFirmwareMetadata(metadata, size); err != nil {
|
||||
return nil, fmt.Errorf("failed to update firmware metadata: %w", err)
|
||||
}
|
||||
|
||||
return &models.UploadResponse{
|
||||
Success: true,
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Size: size,
|
||||
Message: "Metadata updated successfully",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFirmwarePath returns the file path for downloading firmware
|
||||
func (s *FirmwareService) GetFirmwarePath(name, version string) (string, error) {
|
||||
if !s.storage.FirmwareExists(name, version) {
|
||||
return "", fmt.Errorf("firmware not found")
|
||||
}
|
||||
|
||||
return s.storage.GetFirmwareBinaryPath(name, version), nil
|
||||
}
|
||||
|
||||
// groupFirmwareByName groups firmware records by name
|
||||
func (s *FirmwareService) groupFirmwareByName(records []models.FirmwareRecord) []models.GroupedFirmware {
|
||||
nameMap := make(map[string][]models.FirmwareRecord)
|
||||
|
||||
// Group records by name
|
||||
for _, record := range records {
|
||||
nameMap[record.Name] = append(nameMap[record.Name], record)
|
||||
}
|
||||
|
||||
// Convert to slice
|
||||
var grouped []models.GroupedFirmware
|
||||
for name, firmware := range nameMap {
|
||||
// Sort firmware versions by version number (highest first)
|
||||
sort.Slice(firmware, func(i, j int) bool {
|
||||
return compareVersions(firmware[i].Version, firmware[j].Version) > 0
|
||||
})
|
||||
|
||||
grouped = append(grouped, models.GroupedFirmware{
|
||||
Name: name,
|
||||
Firmware: firmware,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort firmware groups by name (A to Z)
|
||||
sort.Slice(grouped, func(i, j int) bool {
|
||||
return grouped[i].Name < grouped[j].Name
|
||||
})
|
||||
|
||||
return grouped
|
||||
}
|
||||
|
||||
// compareVersions compares two semantic version strings
|
||||
// Returns: -1 if v1 < v2, 0 if v1 == v2, 1 if v1 > v2
|
||||
func compareVersions(v1, v2 string) int {
|
||||
// Remove 'v' prefix if present
|
||||
v1 = strings.TrimPrefix(v1, "v")
|
||||
v2 = strings.TrimPrefix(v2, "v")
|
||||
|
||||
parts1 := strings.Split(v1, ".")
|
||||
parts2 := strings.Split(v2, ".")
|
||||
|
||||
// Pad shorter version with zeros
|
||||
maxLen := len(parts1)
|
||||
if len(parts2) > maxLen {
|
||||
maxLen = len(parts2)
|
||||
}
|
||||
|
||||
for len(parts1) < maxLen {
|
||||
parts1 = append(parts1, "0")
|
||||
}
|
||||
for len(parts2) < maxLen {
|
||||
parts2 = append(parts2, "0")
|
||||
}
|
||||
|
||||
// Compare each part
|
||||
for i := 0; i < maxLen; i++ {
|
||||
num1, err1 := strconv.Atoi(parts1[i])
|
||||
num2, err2 := strconv.Atoi(parts2[i])
|
||||
|
||||
// If parsing fails, do string comparison
|
||||
if err1 != nil || err2 != nil {
|
||||
if parts1[i] < parts2[i] {
|
||||
return -1
|
||||
} else if parts1[i] > parts2[i] {
|
||||
return 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if num1 < num2 {
|
||||
return -1
|
||||
} else if num1 > num2 {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user