From 14d58190d3624c87b2a74850af5285c07d60cbfb Mon Sep 17 00:00:00 2001 From: asvegah Date: Fri, 6 Mar 2026 15:48:00 +0000 Subject: [PATCH 01/12] feat: initial work --- .../src/gateways/notes/INotesGateway.ts | 2 + .../src/gateways/notes/NotesGateway.test.ts | 296 ++++++++++++++++++ backoffice/src/gateways/notes/NotesGateway.ts | 45 +++ 3 files changed, 343 insertions(+) create mode 100644 backoffice/src/gateways/notes/NotesGateway.test.ts diff --git a/backoffice/src/gateways/notes/INotesGateway.ts b/backoffice/src/gateways/notes/INotesGateway.ts index 8629e8ca4..0f743755b 100644 --- a/backoffice/src/gateways/notes/INotesGateway.ts +++ b/backoffice/src/gateways/notes/INotesGateway.ts @@ -3,4 +3,6 @@ import { INote } from "../../entities/INote"; export interface INotesGateway { getNotes: (beaconId: string) => Promise; createNote: (note: Partial) => Promise; + updateNote(noteId: string, note: Partial): Promise; + deleteNote(noteId: string): Promise; } diff --git a/backoffice/src/gateways/notes/NotesGateway.test.ts b/backoffice/src/gateways/notes/NotesGateway.test.ts new file mode 100644 index 000000000..df3d0e429 --- /dev/null +++ b/backoffice/src/gateways/notes/NotesGateway.test.ts @@ -0,0 +1,296 @@ +import axios from "axios"; +import { NotesGateway } from "./NotesGateway"; // Adjust path as needed +import { applicationConfig } from "config"; +import { IAuthGateway } from "../auth/IAuthGateway"; +import { INote, NoteType } from "../../entities/INote"; +import { INotesGateway } from "./INotesGateway"; + +jest.mock("axios"); +const mockedAxios = axios as jest.Mocked; + +describe("NotesGateway", () => { + let notesGateway: INotesGateway; + let mockAuthGateway: IAuthGateway; + let accessToken: string; + let config: any; + let beaconId: string; + + beforeEach(() => { + jest.clearAllMocks(); + accessToken = "LET.ME.IN"; + mockAuthGateway = { + getAccessToken: jest.fn().mockResolvedValue(accessToken), + }; + + config = { + timeout: applicationConfig.apiTimeoutMs, + headers: { Authorization: `Bearer ${accessToken}` }, + }; + notesGateway = new NotesGateway(mockAuthGateway); + beaconId = "f48e8212-2e10-4154-95c7-bdfd061bcfd2"; + }); + + describe("getNotes", () => { + it("should make a GET request to the correct endpoint with auth headers", async () => { + const apiResponse = { + data: { + data: [], + }, + }; + mockedAxios.get.mockResolvedValue(apiResponse); + + await notesGateway.getNotes(beaconId); + + expect(mockAuthGateway.getAccessToken).toHaveBeenCalled(); + expect(mockedAxios.get).toHaveBeenCalledWith( + `${applicationConfig.apiUrl}/note?beaconId=${beaconId}`, + config, + ); + }); + + it("should correctly map the API response to INote objects", async () => { + const mockApiDate = "2023-01-01T12:00:00Z"; + const apiResponse = { + data: { + data: [ + { + id: "note-1", + attributes: { + beaconId: beaconId, + text: "Test Note Content", + type: "GENERAL", + createdDate: mockApiDate, + userId: "user-1", + fullName: "User Note", + email: "user@example.com", + }, + }, + ], + }, + }; + + mockedAxios.get.mockResolvedValue(apiResponse); + + const result = await notesGateway.getNotes(beaconId); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + id: "note-1", + beaconId: beaconId, + text: "Test Note Content", + type: NoteType.GENERAL, + createdDate: mockApiDate, + userId: "user-1", + fullName: "User Note", + email: "user@example.com", + }); + }); + + it("should return an empty array if the API returns no data", async () => { + mockedAxios.get.mockResolvedValue({ data: { data: [] } }); + + const result = await notesGateway.getNotes(beaconId); + + expect(result).toEqual([]); + }); + + it("should re-throw errors if the API call fails", async () => { + const error = new Error("Network Error"); + mockedAxios.get.mockRejectedValue(error); + + await expect(notesGateway.getNotes(beaconId)).rejects.toThrow( + "Network Error", + ); + }); + }); + + describe("createNote", () => { + const newNotePartial: Partial = { + beaconId: beaconId, + text: "New Note", + type: NoteType.INCIDENT, + }; + + it("should make a POST request with the correct body structure and headers", async () => { + mockedAxios.post.mockResolvedValue({ + data: { + data: { + id: "new-id", + attributes: { ...newNotePartial }, + }, + }, + }); + + await notesGateway.createNote(newNotePartial); + + expect(mockAuthGateway.getAccessToken).toHaveBeenCalled(); + + const expectedPayload = { + data: { + type: "note", + attributes: { + beaconId: "", + text: newNotePartial.text, + type: newNotePartial.type, + }, + }, + }; + + expect(mockedAxios.post).toHaveBeenCalledWith( + `${applicationConfig.apiUrl}/note`, + expectedPayload, + config, + ); + }); + + it("should map the creation response back to an INote", async () => { + const apiResponse = { + data: { + data: { + id: "note-id", + attributes: { + beaconId: beaconId, + text: "New Note", + type: "INCIDENT", + createdDate: "2023-01-01", + userId: "user-test", + fullName: "Test User", + email: "user@example.com", + }, + }, + }, + }; + mockedAxios.post.mockResolvedValue(apiResponse); + + const result = await notesGateway.createNote(newNotePartial); + + expect(result.id).toBe("note-id"); + expect(result.fullName).toBe("Test User"); + }); + + it("should handle default values for missing attributes in the request payload", async () => { + const emptyNote: Partial = {}; + mockedAxios.post.mockResolvedValue({ + data: { data: { attributes: {} } }, + }); + + try { + await notesGateway.createNote(emptyNote); + } catch (e) { + // Ignore response mapping errors for this specific assertion + } + + const expectedPayload = { + data: { + type: "note", + attributes: { + beaconId: "", + text: "", + type: "", + }, + }, + }; + + expect(mockedAxios.post).toHaveBeenCalledWith( + expect.any(String), + expectedPayload, + expect.any(Object), + ); + }); + + it("should re-throw errors if the POST request fails", async () => { + mockedAxios.post.mockRejectedValue(new Error("500 Server Error")); + + await expect(notesGateway.createNote(newNotePartial)).rejects.toThrow( + "500 Server Error", + ); + }); + }); + + describe("updateNote", () => { + const noteId = "note-123"; + const updateData: Partial = { + text: "Updated text content", + type: NoteType.GENERAL, + }; + + it("should make a PATCH request to the note endpoint with the specific ID", async () => { + mockedAxios.patch.mockResolvedValue({ + data: { + data: { + id: noteId, + attributes: { ...updateData, beaconId }, + }, + }, + }); + + await notesGateway.updateNote(noteId, updateData); + + expect(mockAuthGateway.getAccessToken).toHaveBeenCalled(); + + const expectedUrl = `${applicationConfig.apiUrl}/note/${noteId}`; + + const expectedPayload = { + data: { + type: "note", + attributes: { + beaconId: "", + text: "Updated text content", + type: "GENERAL", + }, + }, + }; + + expect(mockedAxios.patch).toHaveBeenCalledWith( + expectedUrl, + expectedPayload, + config, + ); + }); + + it("should return the updated note object", async () => { + mockedAxios.patch.mockResolvedValue({ + data: { + data: { + id: noteId, + attributes: { + beaconId: beaconId, + text: "Updated text content", + type: "GENERAL", + createdDate: "2023-01-01", + userId: "user-1", + fullName: "Test User", + email: "user@example.com", + }, + }, + }, + }); + + const result = await notesGateway.updateNote(noteId, updateData); + + expect(result.id).toEqual(noteId); + expect(result.text).toEqual("Updated text content"); + }); + }); + + describe("deleteNote", () => { + const noteId = "note-to-delete"; + + it("should make a DELETE request to the specific note endpoint", async () => { + mockedAxios.delete.mockResolvedValue({ status: 204 }); + + await notesGateway.deleteNote(noteId); + + const expectedUrl = `${applicationConfig.apiUrl}/note/${noteId}`; + + expect(mockedAxios.delete).toHaveBeenCalledWith(expectedUrl, config); + }); + + it("should throw an error if the delete fails", async () => { + mockedAxios.delete.mockRejectedValue(new Error("403 Forbidden")); + await expect(notesGateway.deleteNote(noteId)).rejects.toThrow( + "403 Forbidden", + ); + }); + }); +}); diff --git a/backoffice/src/gateways/notes/NotesGateway.ts b/backoffice/src/gateways/notes/NotesGateway.ts index 45a9802ab..f98e33a38 100644 --- a/backoffice/src/gateways/notes/NotesGateway.ts +++ b/backoffice/src/gateways/notes/NotesGateway.ts @@ -33,6 +33,51 @@ export class NotesGateway implements INotesGateway { } } + public async updateNote( + noteId: string, + note: Partial, + ): Promise { + try { + const response = await this._makePatchRequest(`/note/${noteId}`, note); + return this._mapNoteResponseToNote(response.data); + } catch (e) { + throw e; + } + } + + public async deleteNote(noteId: string): Promise { + try { + await this._makeDeleteRequest(`/note/${noteId}`); + } catch (e) { + throw e; + } + } + + private async _makePatchRequest( + path: string, + note: Partial, + ): Promise { + const accessToken = await this._authGateway.getAccessToken(); + + return await axios.patch( + `${applicationConfig.apiUrl}${path}`, + this._mapNoteToNoteRequest(note), // We reuse the existing mapper + { + timeout: applicationConfig.apiTimeoutMs, + headers: { Authorization: `Bearer ${accessToken}` }, + }, + ); + } + + private async _makeDeleteRequest(path: string): Promise { + const accessToken = await this._authGateway.getAccessToken(); + + return await axios.delete(`${applicationConfig.apiUrl}${path}`, { + timeout: applicationConfig.apiTimeoutMs, + headers: { Authorization: `Bearer ${accessToken}` }, + }); + } + private async _makeGetRequest(path: string): Promise { const accessToken = await this._authGateway.getAccessToken(); From 0a663873fc9fa3c87a7760e65e35a248408ef099 Mon Sep 17 00:00:00 2001 From: asvegah Date: Fri, 6 Mar 2026 17:02:45 +0000 Subject: [PATCH 02/12] feat: initial work --- .../api/note/application/NoteService.java | 19 +++++ .../beacons/api/note/mappers/NoteMapper.java | 10 +++ .../beacons/api/note/rest/NoteController.java | 20 +++++ .../beacons/api/note/rest/UpdateNoteDTO.java | 32 ++++++++ .../rest/NoteControllerIntegrationTest.java | 74 ++++++++++++++++++- .../resources/fixtures/updateNoteRequest.json | 14 ++++ 6 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java create mode 100644 service/src/test/resources/fixtures/updateNoteRequest.json diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java b/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java index bf5b1734e..cf8fbdce5 100644 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java @@ -8,7 +8,9 @@ import org.springframework.transaction.annotation.Transactional; import uk.gov.mca.beacons.api.beacon.domain.Beacon; import uk.gov.mca.beacons.api.beacon.domain.BeaconId; +import uk.gov.mca.beacons.api.exceptions.ResourceNotFoundException; import uk.gov.mca.beacons.api.note.domain.Note; +import uk.gov.mca.beacons.api.note.domain.NoteId; import uk.gov.mca.beacons.api.note.domain.NoteRepository; import uk.gov.mca.beacons.api.note.domain.NoteType; import uk.gov.mca.beacons.api.shared.domain.user.User; @@ -28,6 +30,23 @@ public Note create(Note note) { return noteRepository.save(note); } + public Note update(NoteId noteId, Note noteUpdate) { + Note existingNote = noteRepository + .findById(noteId) + .orElseThrow(ResourceNotFoundException::new); + existingNote.setText(noteUpdate.getText()); + existingNote.setType(noteUpdate.getType()); + + return noteRepository.save(existingNote); + } + + public void delete(NoteId noteId) { + if (!noteRepository.existsById(noteId)) { + throw new ResourceNotFoundException(); + } + noteRepository.deleteById(noteId); + } + public List getByBeaconId(BeaconId beaconId) { return noteRepository.findByBeaconId(beaconId); } diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java b/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java index 914ef87a4..f225d2d7e 100644 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java @@ -11,6 +11,7 @@ import uk.gov.mca.beacons.api.note.domain.Note; import uk.gov.mca.beacons.api.note.rest.CreateNoteDTO; import uk.gov.mca.beacons.api.note.rest.NoteDTO; +import uk.gov.mca.beacons.api.note.rest.UpdateNoteDTO; @Component("NoteMapperV2") public class NoteMapper { @@ -24,6 +25,15 @@ public Note fromDTO(CreateNoteDTO dto) { return note; } + public Note fromDTO(UpdateNoteDTO dto) { + var attributes = dto.getAttributes(); + Note note = new Note(); + note.setBeaconId(new BeaconId(attributes.getBeaconId())); + note.setType(attributes.getType()); + note.setText(attributes.getText()); + return note; + } + public NoteDTO toDTO(Note note) { final NoteDTO dto = new NoteDTO(); dto.setId(Objects.requireNonNull(note.getId()).unwrap()); diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java index 50caf7010..04146bcce 100644 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java @@ -6,6 +6,7 @@ import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import uk.gov.mca.beacons.api.auth.application.GetUserService; @@ -13,6 +14,7 @@ import uk.gov.mca.beacons.api.dto.WrapperDTO; import uk.gov.mca.beacons.api.note.application.NoteService; import uk.gov.mca.beacons.api.note.domain.Note; +import uk.gov.mca.beacons.api.note.domain.NoteId; import uk.gov.mca.beacons.api.note.mappers.NoteMapper; import uk.gov.mca.beacons.api.shared.domain.user.User; @@ -57,4 +59,22 @@ public WrapperDTO> getNotesByBeaconId( List notes = noteService.getByBeaconId(beaconId); return noteMapper.toOrderedWrapperDTO(notes); } + + @PatchMapping("/{noteId}") + public ResponseEntity> updateNote( + @PathVariable NoteId noteId, + @RequestBody @Valid WrapperDTO dto + ) { + Note noteUpdate = noteMapper.fromDTO(dto.getData()); + + Note updatedNote = noteService.update(noteId, noteUpdate); + + return ResponseEntity.ok(noteMapper.toWrapperDTO(updatedNote)); + } + + @DeleteMapping("/{noteId}") + public ResponseEntity deleteNote(@PathVariable NoteId noteId) { + noteService.delete(noteId); + return new ResponseEntity<>(HttpStatus.OK); + } } diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java new file mode 100644 index 000000000..338cf680e --- /dev/null +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java @@ -0,0 +1,32 @@ +package uk.gov.mca.beacons.api.note.rest; + +import java.util.UUID; +import javax.validation.constraints.NotNull; +import lombok.*; +import uk.gov.mca.beacons.api.dto.DomainDTO; +import uk.gov.mca.beacons.api.note.domain.NoteType; + +public class UpdateNoteDTO extends DomainDTO { + + private String type = "note"; + + public String getType() { + return type; + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Attributes { + + @NotNull + private UUID beaconId; + + private String text; + + @NotNull + private NoteType type; + } +} diff --git a/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java b/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java index dcb7914fa..708ceaad0 100644 --- a/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java +++ b/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java @@ -1,7 +1,8 @@ package uk.gov.mca.beacons.api.note.rest; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.UUID; -import org.junit.Before; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -54,6 +55,77 @@ void shouldCreateNoteForBeacon() throws Exception { .json(createNoteResponse); } + @Test + void shouldUpdateExistingNote() throws Exception { + String noteId = createNoteAndGetId(beaconId); + + String updateNoteRequest = fixtureHelper.getFixture( + "src/test/resources/fixtures/updateNoteRequest.json", + fixture -> + fixture + .replace("replace-with-test-note-id", noteId) + .replace("replace-with-test-beacon-id", beaconId) + ); + + Mockito.when(getUserService.getUser()).thenReturn(user); + + webTestClient + .patch() + .uri(Endpoints.Note.value + "/" + noteId) + .body(BodyInserters.fromValue(updateNoteRequest)) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("$.data.attributes.text") + .isEqualTo("Updated note text") + .jsonPath("$.data.attributes.type") + .isEqualTo("GENERAL"); + } + + @Test + void shouldDeleteExistingNote() throws Exception { + String noteId = createNoteAndGetId(beaconId); + + Mockito.when(getUserService.getUser()).thenReturn(user); + + webTestClient + .delete() + .uri(Endpoints.Note.value + "/" + noteId) + .exchange() + .expectStatus() + .isOk(); + + webTestClient + .get() + .uri(Endpoints.Note.value + "/" + noteId) + .exchange() + .expectStatus() + .isNotFound(); + } + + private String createNoteAndGetId(String beaconId) throws Exception { + String createNoteRequest = getCreateNoteRequest(beaconId); + Mockito.when(getUserService.getUser()).thenReturn(user); + + byte[] responseBody = webTestClient + .post() + .uri(Endpoints.Note.value) + .body(BodyInserters.fromValue(createNoteRequest)) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .exchange() + .expectStatus() + .isCreated() + .expectBody() + .returnResult() + .getResponseBody(); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(responseBody); + return root.path("data").path("id").asText(); + } + private String getCreateNoteRequest(String beaconId) throws Exception { return fixtureHelper.getFixture( "src/test/resources/fixtures/createNoteRequest.json", diff --git a/service/src/test/resources/fixtures/updateNoteRequest.json b/service/src/test/resources/fixtures/updateNoteRequest.json new file mode 100644 index 000000000..ecba34004 --- /dev/null +++ b/service/src/test/resources/fixtures/updateNoteRequest.json @@ -0,0 +1,14 @@ +{ + "meta": {}, + "included": [], + "data": { + "type": "note", + "id": "replace-with-test-note-id", + "relationships": {}, + "attributes": { + "beaconId": "replace-with-test-beacon-id", + "text": "That's a great beacon right there", + "type": "GENERAL" + } + } +} From 77f7016780b50eb230c436b4e96231d9889e7d40 Mon Sep 17 00:00:00 2001 From: asvegah Date: Fri, 6 Mar 2026 17:49:55 +0000 Subject: [PATCH 03/12] feat: initial work --- .../beacons/api/note/rest/NoteControllerIntegrationTest.java | 4 +++- service/src/test/resources/fixtures/updateNoteRequest.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java b/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java index 708ceaad0..2588add45 100644 --- a/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java +++ b/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java @@ -79,7 +79,7 @@ void shouldUpdateExistingNote() throws Exception { .isOk() .expectBody() .jsonPath("$.data.attributes.text") - .isEqualTo("Updated note text") + .isEqualTo("That's a great beacon right there which has been updated") .jsonPath("$.data.attributes.type") .isEqualTo("GENERAL"); } @@ -107,6 +107,7 @@ void shouldDeleteExistingNote() throws Exception { private String createNoteAndGetId(String beaconId) throws Exception { String createNoteRequest = getCreateNoteRequest(beaconId); + String createNoteResponse = getCreateNoteResponse(beaconId); Mockito.when(getUserService.getUser()).thenReturn(user); byte[] responseBody = webTestClient @@ -118,6 +119,7 @@ private String createNoteAndGetId(String beaconId) throws Exception { .expectStatus() .isCreated() .expectBody() + .json(createNoteResponse) .returnResult() .getResponseBody(); diff --git a/service/src/test/resources/fixtures/updateNoteRequest.json b/service/src/test/resources/fixtures/updateNoteRequest.json index ecba34004..ffacfb805 100644 --- a/service/src/test/resources/fixtures/updateNoteRequest.json +++ b/service/src/test/resources/fixtures/updateNoteRequest.json @@ -7,7 +7,7 @@ "relationships": {}, "attributes": { "beaconId": "replace-with-test-beacon-id", - "text": "That's a great beacon right there", + "text": "That's a great beacon right there which has been updated", "type": "GENERAL" } } From d93de78814c0ec5d5534e58979a07d5091d7c623 Mon Sep 17 00:00:00 2001 From: asvegah Date: Fri, 6 Mar 2026 18:10:35 +0000 Subject: [PATCH 04/12] feat: initial work --- .../api/note/application/NoteService.java | 19 ----- .../beacons/api/note/mappers/NoteMapper.java | 10 --- .../beacons/api/note/rest/NoteController.java | 20 ----- .../beacons/api/note/rest/UpdateNoteDTO.java | 32 -------- .../rest/NoteControllerIntegrationTest.java | 76 +------------------ .../resources/fixtures/updateNoteRequest.json | 14 ---- 6 files changed, 1 insertion(+), 170 deletions(-) delete mode 100644 service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java delete mode 100644 service/src/test/resources/fixtures/updateNoteRequest.json diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java b/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java index cf8fbdce5..bf5b1734e 100644 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/application/NoteService.java @@ -8,9 +8,7 @@ import org.springframework.transaction.annotation.Transactional; import uk.gov.mca.beacons.api.beacon.domain.Beacon; import uk.gov.mca.beacons.api.beacon.domain.BeaconId; -import uk.gov.mca.beacons.api.exceptions.ResourceNotFoundException; import uk.gov.mca.beacons.api.note.domain.Note; -import uk.gov.mca.beacons.api.note.domain.NoteId; import uk.gov.mca.beacons.api.note.domain.NoteRepository; import uk.gov.mca.beacons.api.note.domain.NoteType; import uk.gov.mca.beacons.api.shared.domain.user.User; @@ -30,23 +28,6 @@ public Note create(Note note) { return noteRepository.save(note); } - public Note update(NoteId noteId, Note noteUpdate) { - Note existingNote = noteRepository - .findById(noteId) - .orElseThrow(ResourceNotFoundException::new); - existingNote.setText(noteUpdate.getText()); - existingNote.setType(noteUpdate.getType()); - - return noteRepository.save(existingNote); - } - - public void delete(NoteId noteId) { - if (!noteRepository.existsById(noteId)) { - throw new ResourceNotFoundException(); - } - noteRepository.deleteById(noteId); - } - public List getByBeaconId(BeaconId beaconId) { return noteRepository.findByBeaconId(beaconId); } diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java b/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java index f225d2d7e..914ef87a4 100644 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/mappers/NoteMapper.java @@ -11,7 +11,6 @@ import uk.gov.mca.beacons.api.note.domain.Note; import uk.gov.mca.beacons.api.note.rest.CreateNoteDTO; import uk.gov.mca.beacons.api.note.rest.NoteDTO; -import uk.gov.mca.beacons.api.note.rest.UpdateNoteDTO; @Component("NoteMapperV2") public class NoteMapper { @@ -25,15 +24,6 @@ public Note fromDTO(CreateNoteDTO dto) { return note; } - public Note fromDTO(UpdateNoteDTO dto) { - var attributes = dto.getAttributes(); - Note note = new Note(); - note.setBeaconId(new BeaconId(attributes.getBeaconId())); - note.setType(attributes.getType()); - note.setText(attributes.getText()); - return note; - } - public NoteDTO toDTO(Note note) { final NoteDTO dto = new NoteDTO(); dto.setId(Objects.requireNonNull(note.getId()).unwrap()); diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java index 04146bcce..50caf7010 100644 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java +++ b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/NoteController.java @@ -6,7 +6,6 @@ import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import uk.gov.mca.beacons.api.auth.application.GetUserService; @@ -14,7 +13,6 @@ import uk.gov.mca.beacons.api.dto.WrapperDTO; import uk.gov.mca.beacons.api.note.application.NoteService; import uk.gov.mca.beacons.api.note.domain.Note; -import uk.gov.mca.beacons.api.note.domain.NoteId; import uk.gov.mca.beacons.api.note.mappers.NoteMapper; import uk.gov.mca.beacons.api.shared.domain.user.User; @@ -59,22 +57,4 @@ public WrapperDTO> getNotesByBeaconId( List notes = noteService.getByBeaconId(beaconId); return noteMapper.toOrderedWrapperDTO(notes); } - - @PatchMapping("/{noteId}") - public ResponseEntity> updateNote( - @PathVariable NoteId noteId, - @RequestBody @Valid WrapperDTO dto - ) { - Note noteUpdate = noteMapper.fromDTO(dto.getData()); - - Note updatedNote = noteService.update(noteId, noteUpdate); - - return ResponseEntity.ok(noteMapper.toWrapperDTO(updatedNote)); - } - - @DeleteMapping("/{noteId}") - public ResponseEntity deleteNote(@PathVariable NoteId noteId) { - noteService.delete(noteId); - return new ResponseEntity<>(HttpStatus.OK); - } } diff --git a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java b/service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java deleted file mode 100644 index 338cf680e..000000000 --- a/service/src/main/java/uk/gov/mca/beacons/api/note/rest/UpdateNoteDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package uk.gov.mca.beacons.api.note.rest; - -import java.util.UUID; -import javax.validation.constraints.NotNull; -import lombok.*; -import uk.gov.mca.beacons.api.dto.DomainDTO; -import uk.gov.mca.beacons.api.note.domain.NoteType; - -public class UpdateNoteDTO extends DomainDTO { - - private String type = "note"; - - public String getType() { - return type; - } - - @Getter - @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class Attributes { - - @NotNull - private UUID beaconId; - - private String text; - - @NotNull - private NoteType type; - } -} diff --git a/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java b/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java index 2588add45..dcb7914fa 100644 --- a/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java +++ b/service/src/test/java/uk/gov/mca/beacons/api/note/rest/NoteControllerIntegrationTest.java @@ -1,8 +1,7 @@ package uk.gov.mca.beacons.api.note.rest; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import java.util.UUID; +import org.junit.Before; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -55,79 +54,6 @@ void shouldCreateNoteForBeacon() throws Exception { .json(createNoteResponse); } - @Test - void shouldUpdateExistingNote() throws Exception { - String noteId = createNoteAndGetId(beaconId); - - String updateNoteRequest = fixtureHelper.getFixture( - "src/test/resources/fixtures/updateNoteRequest.json", - fixture -> - fixture - .replace("replace-with-test-note-id", noteId) - .replace("replace-with-test-beacon-id", beaconId) - ); - - Mockito.when(getUserService.getUser()).thenReturn(user); - - webTestClient - .patch() - .uri(Endpoints.Note.value + "/" + noteId) - .body(BodyInserters.fromValue(updateNoteRequest)) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus() - .isOk() - .expectBody() - .jsonPath("$.data.attributes.text") - .isEqualTo("That's a great beacon right there which has been updated") - .jsonPath("$.data.attributes.type") - .isEqualTo("GENERAL"); - } - - @Test - void shouldDeleteExistingNote() throws Exception { - String noteId = createNoteAndGetId(beaconId); - - Mockito.when(getUserService.getUser()).thenReturn(user); - - webTestClient - .delete() - .uri(Endpoints.Note.value + "/" + noteId) - .exchange() - .expectStatus() - .isOk(); - - webTestClient - .get() - .uri(Endpoints.Note.value + "/" + noteId) - .exchange() - .expectStatus() - .isNotFound(); - } - - private String createNoteAndGetId(String beaconId) throws Exception { - String createNoteRequest = getCreateNoteRequest(beaconId); - String createNoteResponse = getCreateNoteResponse(beaconId); - Mockito.when(getUserService.getUser()).thenReturn(user); - - byte[] responseBody = webTestClient - .post() - .uri(Endpoints.Note.value) - .body(BodyInserters.fromValue(createNoteRequest)) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus() - .isCreated() - .expectBody() - .json(createNoteResponse) - .returnResult() - .getResponseBody(); - - ObjectMapper mapper = new ObjectMapper(); - JsonNode root = mapper.readTree(responseBody); - return root.path("data").path("id").asText(); - } - private String getCreateNoteRequest(String beaconId) throws Exception { return fixtureHelper.getFixture( "src/test/resources/fixtures/createNoteRequest.json", diff --git a/service/src/test/resources/fixtures/updateNoteRequest.json b/service/src/test/resources/fixtures/updateNoteRequest.json deleted file mode 100644 index ffacfb805..000000000 --- a/service/src/test/resources/fixtures/updateNoteRequest.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "meta": {}, - "included": [], - "data": { - "type": "note", - "id": "replace-with-test-note-id", - "relationships": {}, - "attributes": { - "beaconId": "replace-with-test-beacon-id", - "text": "That's a great beacon right there which has been updated", - "type": "GENERAL" - } - } -} From cebfbf692ed592c1c31b32cae78a24486dd3fcc5 Mon Sep 17 00:00:00 2001 From: "Will Gibson (Made Tech)" <261894875+willgibson-madetech@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:46:48 +0000 Subject: [PATCH 05/12] Set version of node.js for backoffice --- backoffice/.tool-versions | 1 + 1 file changed, 1 insertion(+) create mode 100644 backoffice/.tool-versions diff --git a/backoffice/.tool-versions b/backoffice/.tool-versions new file mode 100644 index 000000000..9d7091125 --- /dev/null +++ b/backoffice/.tool-versions @@ -0,0 +1 @@ +nodejs 24.11.1 From 55417501e367ae62c4f1f516dd5e210824b5c395 Mon Sep 17 00:00:00 2001 From: "Will Gibson (Made Tech)" <261894875+willgibson-madetech@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:47:17 +0000 Subject: [PATCH 06/12] Set version of Terraform --- .tool-versions | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.tool-versions b/.tool-versions index 066cde3ab..c4aae67d2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -2,3 +2,5 @@ java adoptopenjdk-17.0.15+6 nodejs 20.19.1 +# Todo: This is the first version that supports macOS + arm. Aside from it obviously being a bit behind the times, we should update the pipelines to match. +terraform 1.1.0 From 143f6b7f651ed128c2cc9bbecee722a07fcb334d Mon Sep 17 00:00:00 2001 From: "Will Gibson (Made Tech)" <261894875+willgibson-madetech@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:22:36 +0000 Subject: [PATCH 07/12] Update Makefile so it's easier to follow the output and it installs the things --- Makefile | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index b75191f2c..2207f8f60 100644 --- a/Makefile +++ b/Makefile @@ -5,26 +5,35 @@ # ### -# Run jobs in parallel so we can see log output -MAKEFLAGS += -j - .PHONY: setup setup: setup-root setup-backoffice setup-webapp .PHONY: setup-root setup-root: - @echo "⏭ Installing root level dependencies and commit hooks..." - @cd ./ && npm install + @echo "\n===================================================" + @echo "Installing root level dependencies and commit hooks\n" + cd . && \ + asdf install && \ + node --version && \ + npm install .PHONY: setup-backoffice setup-backoffice: - @echo "⏭ Installing backoffice dependencies..." - @cd ./backoffice && npm install + @echo "\n==================================" + @echo "Installing backoffice dependencies\n" + cd ./backoffice && \ + asdf install && \ + node --version && \ + npm install .PHONY: setup-webapp setup-webapp: - @echo "⏭ Installing webapp dependencies..." - @cd ./webapp && npm install + @echo "\n==============================" + @echo "Installing webapp dependencies\n" + cd ./webapp && \ + asdf install && \ + node --version && \ + npm install ## # Applications From d808e23ef60adc1b435acb97148beab6f7fe3a87 Mon Sep 17 00:00:00 2001 From: "Will Gibson (Made Tech)" <261894875+willgibson-madetech@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:30:13 +0000 Subject: [PATCH 08/12] backoffice/package-lock.json peer dependency updates --- backoffice/package-lock.json | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/backoffice/package-lock.json b/backoffice/package-lock.json index 74d0c5fe4..63cb970ea 100644 --- a/backoffice/package-lock.json +++ b/backoffice/package-lock.json @@ -242,6 +242,7 @@ "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.39.0.tgz", "integrity": "sha512-kks/n2AJzKUk+DBqZhiD+7zeQGBl+WpSOQYzWy6hff3bU0ZrYFqr4keFLlzB5VKuKZog0X59/FGHb1RPBDZLVg==", "license": "MIT", + "peer": true, "dependencies": { "@azure/msal-common": "13.3.3" }, @@ -301,6 +302,7 @@ "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2438,6 +2440,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2461,6 +2464,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2589,6 +2593,7 @@ "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.5.5", "@emotion/cache": "^10.0.27", @@ -2638,6 +2643,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2749,6 +2755,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -5206,6 +5213,7 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -5271,6 +5279,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz", "integrity": "sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/core-downloads-tracker": "^5.17.1", @@ -5466,6 +5475,7 @@ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz", "integrity": "sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.23.9", "@mui/private-theming": "^5.17.1", @@ -6785,7 +6795,6 @@ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -6806,7 +6815,6 @@ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -6823,7 +6831,6 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7164,6 +7171,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -7185,6 +7193,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.85.tgz", "integrity": "sha512-5oBDUsRDsrYq4DdyHaL99gE1AJCfuDhyxqF6/55fvvOIRkp1PpKuwJ+aMiGJR+GJt7YqMNclPROTHF20vY2cXA==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", @@ -7196,6 +7205,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz", "integrity": "sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^17.0.0" } @@ -7322,6 +7332,7 @@ "integrity": "sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.32.1", "@typescript-eslint/types": "8.32.1", @@ -7526,6 +7537,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8532,6 +8544,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -10070,6 +10083,7 @@ "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -14202,6 +14216,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -15906,6 +15921,7 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -16072,6 +16088,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -16097,6 +16114,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -16249,6 +16267,7 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -16490,6 +16509,7 @@ "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.7" }, @@ -16694,6 +16714,7 @@ "integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -17535,6 +17556,7 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17823,6 +17845,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18081,6 +18104,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -18227,6 +18251,7 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, From 3c4621c7307a07170b265bbbf9235e439b64ff3f Mon Sep 17 00:00:00 2001 From: Will Fort Date: Tue, 10 Mar 2026 17:19:52 +0000 Subject: [PATCH 09/12] Makefile now runs all services --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2207f8f60..1cc5424b1 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,12 @@ setup-webapp: # Applications ## .PHONY: serve -serve: serve-backing-services serve-webapp serve-backoffice serve-backoffice-stubs +serve: + @docker compose up postgres redis opensearch opensearch-proxy opensearch-dashboards service --build & \ + (cd ./webapp && npm run dev) & \ + (cd ./backoffice && npm run start) & \ + node ./backoffice/stubs.js & \ + wait .PHONY: serve-webapp serve-webapp: From 5b727d43845b1496a1a25fe412a59f7d903c7eb3 Mon Sep 17 00:00:00 2001 From: Will Fort Date: Tue, 17 Mar 2026 12:00:42 +0000 Subject: [PATCH 10/12] added back parallel docker-compose services and reverted change --- Makefile | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 1cc5424b1..a9bcb284c 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ # ### +MAKEFLAGS += -j + .PHONY: setup setup: setup-root setup-backoffice setup-webapp @@ -38,13 +40,10 @@ setup-webapp: ## # Applications ## + +# Parralel docker-compose .PHONY: serve -serve: - @docker compose up postgres redis opensearch opensearch-proxy opensearch-dashboards service --build & \ - (cd ./webapp && npm run dev) & \ - (cd ./backoffice && npm run start) & \ - node ./backoffice/stubs.js & \ - wait +serve: serve-backing-services serve-webapp serve-backoffice serve-backoffice-stubs .PHONY: serve-webapp serve-webapp: From 86c429fd2fc1b454b8d308937d1f91d9cdba52cf Mon Sep 17 00:00:00 2001 From: Will Fort Date: Tue, 17 Mar 2026 12:19:08 +0000 Subject: [PATCH 11/12] added install for node and asdf --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index a9bcb284c..a7d7b569b 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ MAKEFLAGS += -j .PHONY: setup setup: setup-root setup-backoffice setup-webapp + brew install asdf + asdf plugin add nodejs .PHONY: setup-root setup-root: From cd8b5a04c9d566ad59985a4bea98835975708935 Mon Sep 17 00:00:00 2001 From: Will Fort Date: Mon, 30 Mar 2026 09:01:16 +0100 Subject: [PATCH 12/12] Added edit and delete buttons to notes for backoffice --- backoffice/src/panels/notesPanel/NotesViewing.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backoffice/src/panels/notesPanel/NotesViewing.tsx b/backoffice/src/panels/notesPanel/NotesViewing.tsx index b215bc931..698c1a828 100644 --- a/backoffice/src/panels/notesPanel/NotesViewing.tsx +++ b/backoffice/src/panels/notesPanel/NotesViewing.tsx @@ -1,4 +1,5 @@ import { + Button, CardHeader, Table, TableBody, @@ -36,6 +37,7 @@ export const NotesViewing: FunctionComponent = ({ Type of note Note Noted by + @@ -45,6 +47,12 @@ export const NotesViewing: FunctionComponent = ({ {titleCase(note.type)} {note.text} {note.fullName} + + + + ))}