diff --git a/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- false.bru b/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- false.bru new file mode 100644 index 0000000..59b4b1a --- /dev/null +++ b/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- false.bru @@ -0,0 +1,31 @@ +meta { + name: dronesWithCooling -> false + type: http + seq: 1 +} + +get { + url: {{API_BASE}}/dronesWithCooling/false + body: none + auth: inherit +} + +tests { + test("Status code is 200", function() { + expect(res.status).to.equal(200); + }); + + test("Response body is a JSON array", function() { + expect(res.getBody()).to.be.an('array'); + }); + + test("Array is not empty and contains numbers", function() { + const data = res.getBody(); + expect(data[0]).to.be.a('number'); // data should be in number + }); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- illegal.bru b/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- illegal.bru new file mode 100644 index 0000000..949d45e --- /dev/null +++ b/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- illegal.bru @@ -0,0 +1,24 @@ +meta { + name: dronesWithCooling -> illegal + type: http + seq: 1 +} + +get { + url: {{API_BASE}}/dronesWithCooling/illegal + body: none + auth: inherit +} + +tests { + test("Status code is 400", function() { + expect(res.status).to.equal(400); + }); + + +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- true.bru b/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- true.bru new file mode 100644 index 0000000..295e3a2 --- /dev/null +++ b/ilp-cw-api/[GET] dronesWithCooling/dronesWithCooling -- true.bru @@ -0,0 +1,31 @@ +meta { + name: dronesWithCooling -> true + type: http + seq: 1 +} + +get { + url: {{API_BASE}}/dronesWithCooling/true + body: none + auth: inherit +} + +tests { + test("Status code is 200", function() { + expect(res.status).to.equal(200); + }); + + test("Response body is a JSON array", function() { + expect(res.getBody()).to.be.an('array'); + }); + + test("Array is not empty and contains numbers", function() { + const data = res.getBody(); + expect(data[0]).to.be.a('number'); // data should be in number + }); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/ilp-cw-api/[GET] dronesWithCooling/folder.bru b/ilp-cw-api/[GET] dronesWithCooling/folder.bru new file mode 100644 index 0000000..2c9f0d3 --- /dev/null +++ b/ilp-cw-api/[GET] dronesWithCooling/folder.bru @@ -0,0 +1,8 @@ +meta { + name: [GET] dronesWithCooling + seq: 1 +} + +auth { + mode: inherit +} diff --git a/ilp-cw-api/bruno.json b/ilp-cw-api/bruno.json new file mode 100644 index 0000000..1fcef84 --- /dev/null +++ b/ilp-cw-api/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "ILP CW API Collection", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/ilp-cw-api/collection.bru b/ilp-cw-api/collection.bru new file mode 100644 index 0000000..ad0a499 --- /dev/null +++ b/ilp-cw-api/collection.bru @@ -0,0 +1,4 @@ +vars:pre-request { + BASE_URL: http://localhost:8080 + API_BASE: {{BASE_URL}}/api/v1 +} diff --git a/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java b/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java index 51aeb63..7eb937f 100644 --- a/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java +++ b/src/main/java/io/github/js0ny/ilp_coursework/controller/ApiController.java @@ -16,7 +16,7 @@ import io.github.js0ny.ilp_coursework.service.GpsCalculationService; /** * Main REST Controller for the ILP Coursework 1 application. *

- * This class handles all incoming HTTP requests for the API under {@code /api/v1} path. + * This class handles incoming HTTP requests for the API under {@code /api/v1} path (defined in CW1) * This is responsible for mapping requests to the appropriate service method and returning the results as responses. * The business logic is delegated to {@link GpsCalculationService} */ diff --git a/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java b/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java index e535b7f..411e3fe 100644 --- a/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java +++ b/src/main/java/io/github/js0ny/ilp_coursework/controller/DroneController.java @@ -8,44 +8,51 @@ import org.springframework.web.client.RestTemplate; import java.util.Arrays; +/** + * Main Rest Controller for the ILP Coursework 2 application. + *

+ * This class handles incoming HTTP requests for the API under {@code /api/v1} path (defined in CW2) + * The business logic is delegated to {@link DroneInfoService} + */ @RestController @RequestMapping("/api/v1") public class DroneController { private final DroneInfoService droneService; - private final String baseUrl; private final RestTemplate restTemplate = new RestTemplate(); + /** + * Constructor of the {@code DroneController} with the business logic dependency {@code DroneInfoService} + *

+ * We handle the {@code baseUrl} here. Use a predefined URL if the environment variable {@code ILP_ENDPOINT} + * is not given. + * + * @param droneService The service component that contains all business logic + */ public DroneController(DroneInfoService droneService) { this.droneService = droneService; - String baseUrl = System.getenv("ILP_ENDPOINT"); - if (baseUrl == null || baseUrl.isBlank()) { - this.baseUrl = "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/"; - } else { - if (!baseUrl.endsWith("/")) { - baseUrl += "/"; - } - this.baseUrl = baseUrl; - } } + /** + * Handles GET requests to retrieve an array of drones (identified by id) that has the capability of cooling + * + * @param state The path variable that indicates the return should have or not have the capability + * @return An array of drone id with cooling capability. + */ @GetMapping("/dronesWithCooling/{state}") - - public int[] getDronesWithCoolingAbility(@PathVariable boolean state) { - - String droneUrl = baseUrl + "drones"; - - DroneDto[] drones = restTemplate.getForObject(droneUrl, DroneDto[].class); - - if (drones == null) { - return new int[]{}; - } - - return Arrays.stream(drones). - filter(drone -> drone.capability().cooling() == state). - mapToInt(drone -> Integer.parseInt(String.valueOf(drone.id()))). - toArray(); + public int[] getDronesWithCoolingCapability(@PathVariable boolean state) { + return droneService.dronesWithCooling(state); } +// @GetMapping("/droneDetails/{id}") +// public DroneDto getDroneDetail(@PathVariable int id) { +// String droneUrl = baseUrl + "drones"; +// +// DroneDto[] drones = restTemplate.getForObject(droneUrl, DroneDto[].class); +// +// return new DroneDto(); +// +// } + @PostMapping("queryAvailableDrones") public int queryAvailableDrones() { return 1; diff --git a/src/main/java/io/github/js0ny/ilp_coursework/data/DroneDto.java b/src/main/java/io/github/js0ny/ilp_coursework/data/DroneDto.java index 8282573..1997d26 100644 --- a/src/main/java/io/github/js0ny/ilp_coursework/data/DroneDto.java +++ b/src/main/java/io/github/js0ny/ilp_coursework/data/DroneDto.java @@ -5,6 +5,6 @@ package io.github.js0ny.ilp_coursework.data; */ public record DroneDto( String name, - int id, + String id, DroneCapabilityDto capability) { } diff --git a/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java b/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java index 9ac70eb..c70862c 100644 --- a/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java +++ b/src/main/java/io/github/js0ny/ilp_coursework/service/DroneInfoService.java @@ -1,8 +1,73 @@ package io.github.js0ny.ilp_coursework.service; +import io.github.js0ny.ilp_coursework.data.DroneDto; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriBuilder; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.Arrays; +import java.util.stream.Stream; @Service public class DroneInfoService { + private final String baseUrl; + private final String dronesEndpoint = "drones"; + + private final RestTemplate restTemplate = new RestTemplate(); + + /** + * Constructor, handles the base url here. + */ + public DroneInfoService() { + + String baseUrl = System.getenv("ILP_ENDPOINT"); + if (baseUrl == null || baseUrl.isBlank()) { + this.baseUrl = "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/"; + } else { + // Defensive: Add '/' to the end of the URL + if (!baseUrl.endsWith("/")) { + baseUrl += "/"; + } + this.baseUrl = baseUrl; + } + } + + /** + * Return an array of ids of drones with/without cooling capability + * + * @param state determines the capability filtering + * @return if {@code state} is true, return ids of drones with cooling capability, else without cooling + */ + public int[] dronesWithCooling(boolean state) { + URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint); + + DroneDto[] drones = restTemplate.getForObject(droneUrl, DroneDto[].class); + + if (drones == null) { + return new int[]{}; + } + + return Arrays.stream(drones). + filter(drone -> drone.capability().cooling() == state). + mapToInt(drone -> Integer.parseInt(drone.id())). + toArray(); + } + + // TODO: This is function is WIP + public Stream droneDetail(int id) { + String droneUrl = baseUrl + dronesEndpoint; + + DroneDto[] drones = restTemplate.getForObject(droneUrl, DroneDto[].class); + + if (drones == null) { + throw new IllegalArgumentException("drone with that ID cannot be found"); + } + + return Arrays.stream(drones). + filter(drone -> Integer.parseInt(String.valueOf(drone.id())) == id); + + } }