diff --git a/api/openapi.yaml b/api/openapi.yaml index 4e09433..09131aa 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -201,6 +201,60 @@ paths: schema: type: string + delete: + summary: Delete firmware binary + description: | + Delete the firmware binary and metadata for the specified name and version. + This action cannot be undone. + operationId: deleteFirmware + parameters: + - name: name + in: path + description: Firmware name + required: true + schema: + type: string + example: "base" + - name: version + in: path + description: Firmware version (semantic versioning) + required: true + schema: + type: string + example: "1.0.0" + responses: + '200': + description: Firmware deleted successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + message: + type: string + example: "Firmware deleted successfully" + name: + type: string + example: "base" + version: + type: string + example: "1.0.0" + '404': + description: Firmware not found + content: + text/plain: + schema: + type: string + '500': + description: Internal server error + content: + text/plain: + schema: + type: string + /health: get: summary: Health check diff --git a/internal/app/app.go b/internal/app/app.go index d8597b6..2dbbb20 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -72,6 +72,7 @@ func (a *App) SetupRoutes() *http.ServeMux { mux.HandleFunc("PUT /firmware/{name}/{version}", a.handler.UpdateFirmwareMetadata) mux.HandleFunc("GET /firmware", a.handler.ListFirmware) mux.HandleFunc("GET /firmware/{name}/{version}", a.handler.DownloadFirmware) + mux.HandleFunc("DELETE /firmware/{name}/{version}", a.handler.DeleteFirmware) // Health check endpoint mux.HandleFunc("GET /health", a.handler.HealthCheck) diff --git a/internal/handlers/firmware.go b/internal/handlers/firmware.go index f4862cb..244d480 100644 --- a/internal/handlers/firmware.go +++ b/internal/handlers/firmware.go @@ -199,6 +199,52 @@ func (h *FirmwareHandler) DownloadFirmware(w http.ResponseWriter, r *http.Reques http.ServeFile(w, r, filePath) } +// DeleteFirmware handles DELETE /firmware/{name}/{version} endpoint +func (h *FirmwareHandler) DeleteFirmware(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodDelete { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Extract path parameters + path := strings.TrimPrefix(r.URL.Path, "/firmware/") + parts := strings.Split(path, "/") + if len(parts) != 2 { + log.Printf("Invalid firmware path for delete: %s", r.URL.Path) + http.Error(w, "Invalid firmware path", http.StatusBadRequest) + return + } + + name := parts[0] + version := parts[1] + + log.Printf("DELETE /firmware/%s/%s - %s", name, version, r.RemoteAddr) + + // Delete firmware using service + if err := h.service.DeleteFirmware(name, version); err != nil { + if strings.Contains(err.Error(), "not found") { + log.Printf("Firmware not found for deletion: %s/%s", name, version) + http.Error(w, "Firmware not found", http.StatusNotFound) + } else { + log.Printf("Failed to delete firmware: %v", err) + http.Error(w, "Failed to delete firmware: "+err.Error(), http.StatusInternalServerError) + } + return + } + + log.Printf("Successfully deleted firmware: %s/%s", name, version) + + // Return success response + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Firmware deleted successfully", + "name": name, + "version": version, + }) +} + // HealthCheck provides a simple health check endpoint func (h *FirmwareHandler) HealthCheck(w http.ResponseWriter, r *http.Request) { response := models.HealthResponse{Status: "healthy"} diff --git a/internal/repository/firmware.go b/internal/repository/firmware.go index 27267e9..de481b1 100644 --- a/internal/repository/firmware.go +++ b/internal/repository/firmware.go @@ -80,3 +80,23 @@ func (r *FirmwareRepository) GetFirmwareRecords(name, version string) ([]models. return records, nil } + +// DeleteFirmwareMetadata deletes firmware metadata from the database +func (r *FirmwareRepository) DeleteFirmwareMetadata(name, version string) error { + query := `DELETE FROM firmware WHERE name = ? AND version = ?` + result, err := r.db.Exec(query, name, version) + if err != nil { + return fmt.Errorf("failed to delete firmware metadata: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } + + if rowsAffected == 0 { + return fmt.Errorf("firmware not found") + } + + return nil +} diff --git a/internal/service/firmware.go b/internal/service/firmware.go index a1ff408..97199e8 100644 --- a/internal/service/firmware.go +++ b/internal/service/firmware.go @@ -110,6 +110,31 @@ func (s *FirmwareService) GetFirmwarePath(name, version string) (string, error) return s.storage.GetFirmwareBinaryPath(name, version), nil } +// DeleteFirmware deletes firmware binary and metadata +func (s *FirmwareService) DeleteFirmware(name, version string) error { + // Validate required fields + if name == "" || version == "" { + return fmt.Errorf("name and version are required") + } + + // Check if firmware exists + if !s.storage.FirmwareExists(name, version) { + return fmt.Errorf("firmware not found") + } + + // Delete from database first + if err := s.repo.DeleteFirmwareMetadata(name, version); err != nil { + return fmt.Errorf("failed to delete firmware metadata: %w", err) + } + + // Delete firmware binary + if err := s.storage.DeleteFirmwareBinary(name, version); err != nil { + return fmt.Errorf("failed to delete firmware binary: %w", err) + } + + return nil +} + // groupFirmwareByName groups firmware records by name func (s *FirmwareService) groupFirmwareByName(records []models.FirmwareRecord) []models.GroupedFirmware { nameMap := make(map[string][]models.FirmwareRecord) diff --git a/internal/storage/file.go b/internal/storage/file.go index e68b571..c29c377 100644 --- a/internal/storage/file.go +++ b/internal/storage/file.go @@ -55,3 +55,20 @@ func (fs *FileStorage) FirmwareExists(name, version string) bool { _, err := os.Stat(filePath) return !os.IsNotExist(err) } + +// DeleteFirmwareBinary deletes a firmware binary and its directory +func (fs *FileStorage) DeleteFirmwareBinary(name, version string) error { + // Get directory path: registry/// + dirPath := filepath.Join(fs.registryPath, name, version) + + // Remove the entire version directory + if err := os.RemoveAll(dirPath); err != nil { + return fmt.Errorf("failed to delete firmware directory: %w", err) + } + + // Try to remove the name directory if it's empty + nameDir := filepath.Join(fs.registryPath, name) + os.Remove(nameDir) // Ignore error, directory might not be empty + + return nil +}