add session-based connection management and React dashboard

Backend: adds JDBC session support, login/status/logout endpoints, and
new DTOs (AuthResponse, ConnectionStatusResponse, LoginResult). Frontend
replaces the Vite boilerplate with a Dashboard, ServiceCard, and
ConnectModal backed by a typed API client.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 03:30:36 +02:00
parent 0127706262
commit 43bbcece7a
30 changed files with 1307 additions and 350 deletions
@@ -8,6 +8,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import com.github.tomakehurst.wiremock.http.Fault;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
@@ -41,18 +42,16 @@ class HomeboxIntegrationTest {
}
""";
private static final String MOCK_URL = "http://localhost:1234";
private static final String TEST_USER = "admin";
private static final String TEST_PASS = "pw";
/**
* Returns Token and status code OK when login is successful.
*
* @param wm
* the WiremockRuntimeInfo object
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldReturnTokenAndStatusOkWhenHomeboxCredentialsAreValid(WireMockRuntimeInfo wm) {
void shouldReturnStatusOkWhenHomeboxCredentialsAreValid(WireMockRuntimeInfo wm) {
stubFor(post(HOMEBOX_LOGIN.getValue())
.willReturn(okJson(okJsonHomeboxResponse)));
@@ -62,11 +61,8 @@ class HomeboxIntegrationTest {
DocumentContext documentContext = JsonPath.parse(response.getBody());
String token = documentContext.read("$.token");
assertThat(token).isEqualTo("fake-jwt-token");
String attachmentToken = documentContext.read("$.extraResponseData.attachmentToken");
assertThat(attachmentToken).isEqualTo("fake-attach");
String serviceType = documentContext.read("$.serviceType");
assertThat(serviceType).isEqualTo("HOMEBOX");
String expiresAt = documentContext.read("$.expiresAt", String.class);
assertThat(expiresAt).isEqualTo("2026-04-26T02:23:13Z");
@@ -76,8 +72,7 @@ class HomeboxIntegrationTest {
/**
* Tests the Unauthorized custom exception.
*
* @param wm
* the WiremockRuntimeInfo object
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldFailToConnectWhenHomeboxReturnsUnauthorized(WireMockRuntimeInfo wm) {
@@ -94,8 +89,7 @@ class HomeboxIntegrationTest {
/**
* Tests a server error from the external api.
*
* @param wm
* the WiremockRuntimeInfo object
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldFailToConnectWhenHomeboxReturnsServiceUnavailable(WireMockRuntimeInfo wm) {
@@ -113,14 +107,12 @@ class HomeboxIntegrationTest {
* Checks when the service is unavailable or the app URL is wrong.
*/
@Test
void shouldReturnServiceUnavailableWhenHomeboxUrlIsWrong() {
void shouldReturnServiceUnavailableWhenHomeboxUrlIsWrong(WireMockRuntimeInfo wm) {
ConnectionRequest badRequest = new ConnectionRequest(
MOCK_URL,
HOMEBOX.getValue(),
TEST_USER, TEST_PASS,
false);
stubFor(post(HOMEBOX_LOGIN.getValue())
.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));
ConnectionRequest badRequest = connectionRequest(wm);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(), badRequest, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
@@ -146,9 +138,9 @@ class HomeboxIntegrationTest {
* unsupported.
*/
@Test
void shouldReturnWrongServiceTypeException() {
void shouldReturnWrongServiceTypeException(WireMockRuntimeInfo wm) {
ConnectionRequest wrongServiceTypeReq = new ConnectionRequest(
MOCK_URL,
wm.getHttpBaseUrl(),
"wrong-service-type",
TEST_USER, TEST_PASS,
false);
@@ -164,8 +156,7 @@ class HomeboxIntegrationTest {
* Tests the succesfull persistance of Homebox credential response to the
* database.
*
* @param wm
* the WiremockRuntimeInfo object
* @param wm the WiremockRuntimeInfo object
*/
@Test
void shouldSaveHomeboxConnectionResponseToDb(WireMockRuntimeInfo wm) {
@@ -176,26 +167,25 @@ class HomeboxIntegrationTest {
ConnectionRequest request = connectionRequest(wm);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(), request, String.class);
DocumentContext responseContext = JsonPath.parse(response.getBody());
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
ConnectionEntity dbEntry = cRepository.findByAppUrlAndUsername(request.appUrl(), request.username());
assertThat(dbEntry).isNotNull();
assertThat(dbEntry.getAppUrl()).isEqualTo(request.appUrl());
assertThat(dbEntry.getUsername()).isEqualTo(request.username());
if (dbEntry instanceof HomeboxEntity hbE) {
assertThat(hbE.getToken()).isEqualTo(responseContext.read("$.token"));
assertThat(hbE.getAttachmentToken()).isEqualTo(responseContext.read("$.extraResponseData.attachmentToken"));
assertThat(hbE.getExpiresAt()).isEqualTo(responseContext.read("$.expiresAt"));
assertThat(hbE.getToken()).isEqualTo("fake-jwt-token");
assertThat(hbE.getAttachmentToken()).isEqualTo("fake-attach");
assertThat(hbE.getExpiresAt().toString()).hasToString("2026-04-26T02:23:13Z");
}
}
@Test
void shouldReturnEmptyCredentialsExceptionWhenCredsAreMissing() {
ConnectionRequest missingCredentials = new ConnectionRequest(MOCK_URL, HOMEBOX.getValue(), TEST_USER, null,
void shouldReturnEmptyCredentialsExceptionWhenCredsAreMissing(WireMockRuntimeInfo wm) {
ConnectionRequest missingCredentials = new ConnectionRequest(wm.getHttpBaseUrl(), HOMEBOX.getValue(), TEST_USER,
null,
false);
ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(), missingCredentials,
@@ -206,11 +196,10 @@ class HomeboxIntegrationTest {
}
/**
* Creates a valid connection request with a mock Api throuh
* Creates a valid connection request with a mock Api through
* WireMockRuntimeInfo.
*
* @param wm
* the WiremockRuntimeInfo object
* @param wm the WiremockRuntimeInfo object
* @return a mock api connection request.
*/
private ConnectionRequest connectionRequest(WireMockRuntimeInfo wm) {