Skip to content
Merged
82 changes: 40 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WPI Core

**WPI Core** is a Java library providing foundational abstractions for secure API communication, including authentication, endpoint access control, and request/response modeling, designed to be consumed by higher-level WPI client implementations.
**WPI Core** is a Java library providing foundational abstractions for secure API communication, including authentication, endpoint access control, request/response modeling, and application-level service contracts for WPI clients.

Part of the **Waterflow Pixel (WP)** ecosystem developed by [VerbaTechTeam](https://github.com/VerbaTechTeam) — *We create words that devices understand.*

Expand All @@ -12,7 +12,7 @@ Part of the **Waterflow Pixel (WP)** ecosystem developed by [VerbaTechTeam](http

| Repository | Layer | Description |
|---|---|---|
| **wpi-core** *(this repo)* | WPI | Core API abstractions in Java — auth, endpoints, request model |
| **wpi-core** *(this repo)* | WPI | Core API abstractions in Java — auth, endpoints, request model, service contracts |
| [waterflow-pixel-unit](https://github.com/VerbaTechTeam/waterflow-pixel-unit) | WPU | MicroPython firmware for Raspberry Pi Pico 2W with built-in REST HTTP server for direct LED strip control |

For a full overview of the system architecture and roadmap (including the planned **WPC** controller layer and **wpu-emulator**), see the [VerbaTechTeam organization profile](https://github.com/VerbaTechTeam).
Expand Down Expand Up @@ -48,39 +48,35 @@ Then reference it in your project's `pom.xml`:

## Architecture

The library follows a clean, layered architecture:
The library follows a layered architecture:

```
```text
pl.vtt.wpi.core
├── application
│ ├── config # AuthorizationHolder (thread-local auth state)
│ ├── exception # Domain exceptions (e.g. IncorrectUsernameOrPasswordException)
│ ├── service # Service interfaces (LoginService)
│ │ └── impl # Internal implementations (not exported)
│ └── util # RequestFactory, RequestHandler interfaces
└── domain
└── model
├── Authorization.java
├── Credentials.java
├── Request.java
└── endpoint
├── EndpointDescriptor.java
├── Method.java
├── RequestTarget.java
├── Resource.java
└── UserGroup.java
│ ├── exception # Application-level exceptions
│ └── service
│ ├── ... # Service interfaces (LoginService, RuntimeDataService, etc.)
│ └── impl # Internal service implementations
├── domain
│ ├── dto
│ ├── model
│ └── port # Input/Output port contracts + endpoint-specific port implementations
└── infrastructure
├── Request / Response
├── RequestFactory / RequestHandler / RequestSender
└── factory # SynchronizedRequestFactory
```

## Key Concepts

### Authentication

Login is handled by `LoginService`. Internally, `LoginServiceImpl` delegates authorization to a dedicated port (`AuthOutputPort`), so request execution is decoupled from service orchestration.
Login is handled by `LoginService`. Internally, `LoginServiceImpl` delegates authorization to a dedicated output port (`AuthOutputPort`), so request execution is decoupled from service orchestration.

On success, the resulting `Credentials` (username + token) are stored as a Basic Auth header in `AuthorizationHolder` — a thread-local holder used to attach authorization to outgoing requests.
On success, resulting `Credentials` (username + token) are stored in `AuthorizationHolder` — a thread-local holder used to attach authorization to outgoing requests.

```java
// Provided by a higher-level module
LoginService loginService = ...;

Credentials credentials = loginService.login("admin", "password");
Expand Down Expand Up @@ -108,29 +104,37 @@ boolean allowed = RequestTarget.DATA_UPDATE.allow(Method.PUT, userGroups);

### Request Model

A `Request<T>` carries the target URL, authorization header, and an optional typed payload:
A `Request<T>` carries timestamp, HTTP method, target URL, authorization header, and an optional typed payload:

```java
record Request<T>(Method method, String url, Authorization authorization, T payload) {}
Request<T> request = new Request<>(
null, // timestamp (null => now)
Method.GET,
"https://host/api/data",
authorization,
payload
);
```

### Implementing a Client

`wpi-core` exposes only the `LoginService` interface — the implementation is internal to the module. A higher-level module (e.g. `wpi-desktop`) is responsible for instantiating the service and exposing it to the client.
### Application Services

From the client's perspective, usage is limited to the public interface:
The package `pl.vtt.wpi.core.application.service` currently exposes interfaces for:

```java
// Provided by a higher-level module
LoginService loginService = ...;
- `LoginService`
- `AdminPasswordService`
- `UserManagementService`
- `RuntimeDataService`
- `PixelProgramService`
- `NetworkConfigurationService`
- `DeviceInfoService`
- `DebugService`
- `RebootService`

Credentials credentials = loginService.login("admin", "password");
loginService.logout();
```
Concrete implementations are provided in `pl.vtt.wpi.core.application.service.impl`.

### Domain Ports

The package `pl.vtt.wpi.core.domain.port` contains concrete port implementations for domain operations:
The package `pl.vtt.wpi.core.domain.port` contains port contracts and endpoint-specific implementations:

- **Output ports**: `AuthOutputPort`, `DeviceInfoOutputPort`, `RuntimeDataOutputPort`, `CurrentStateOutputPort`, `PixelProgramsOutputPort`, `UsersOutputPort`
- **Input ports**: `RuntimeDataInputPort`, `WifiConfigInputPort`, `PixelProgramsInputPort`, `UserCreateInputPort`, `RestartInputPort`, `LogsDeleteInputPort`
Expand All @@ -147,13 +151,7 @@ mvn test
mvn package
```

Unit tests are written with JUnit Jupiter 5 and currently verify:

- successful login flow (`Credentials` returned + Authorization header updated),
- validation for null/blank username and password,
- incorrect credentials / null auth response handling,
- cleanup of `AuthorizationHolder` after failed login attempts (including runtime failures),
- logout behavior.
Unit tests are written with JUnit Jupiter 5 and currently include coverage for port-level behavior and application-service logic (including `UserCreateInputPort` / `UserCreateRequest`, login flow, and related service orchestration paths).

## CI

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class AdminPasswordResetException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public AdminPasswordResetException(String message) {
super(message);
}

public AdminPasswordResetException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public abstract class ApplicationServiceException extends Exception {
private static final long serialVersionUID = 1L;

protected ApplicationServiceException(String message) {
super(message);
}

protected ApplicationServiceException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class AuthenticationServiceUnavailableException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public AuthenticationServiceUnavailableException(String message) {
super(message);
}

public AuthenticationServiceUnavailableException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class DebugDataAccessException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public DebugDataAccessException(String message) {
super(message);
}

public DebugDataAccessException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class DeviceInfoReadException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public DeviceInfoReadException(String message) {
super(message);
}

public DeviceInfoReadException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class DeviceRebootException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public DeviceRebootException(String message) {
super(message);
}

public DeviceRebootException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class NetworkConfigurationException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public NetworkConfigurationException(String message) {
super(message);
}

public NetworkConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class PixelProgramOperationException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public PixelProgramOperationException(String message) {
super(message);
}

public PixelProgramOperationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class RuntimeDataOperationException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public RuntimeDataOperationException(String message) {
super(message);
}

public RuntimeDataOperationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package pl.vtt.wpi.core.application.exception;

public class UserManagementOperationException extends ApplicationServiceException {
private static final long serialVersionUID = 1L;

public UserManagementOperationException(String message) {
super(message);
}

public UserManagementOperationException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package pl.vtt.wpi.core.application.service;

import pl.vtt.wpi.core.domain.dto.PasswordDto;
import pl.vtt.wpi.core.application.exception.AdminPasswordResetException;

public interface AdminPasswordService {
void resetPassword(String secureKey, PasswordDto passwordDto);
void resetPassword(String secureKey, PasswordDto passwordDto) throws AdminPasswordResetException;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package pl.vtt.wpi.core.application.service;

import pl.vtt.wpi.core.domain.model.device.CurrentState;
import pl.vtt.wpi.core.application.exception.DebugDataAccessException;

import java.util.List;

public interface DebugService {
List<String> pollLogs();
List<String> peekLogs();
CurrentState getCurrentState();
List<String> pollLogs() throws DebugDataAccessException;
List<String> peekLogs() throws DebugDataAccessException;
CurrentState getCurrentState() throws DebugDataAccessException;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package pl.vtt.wpi.core.application.service;

import pl.vtt.wpi.core.domain.model.device.DeviceInfo;
import pl.vtt.wpi.core.application.exception.DeviceInfoReadException;

public interface DeviceInfoService {
DeviceInfo read();
DeviceInfo read() throws DeviceInfoReadException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import pl.vtt.wpi.core.application.config.AuthorizationHolder;
import pl.vtt.wpi.core.application.exception.IncorrectUsernameOrPasswordException;
import pl.vtt.wpi.core.application.exception.AuthenticationServiceUnavailableException;
import pl.vtt.wpi.core.domain.model.Credentials;

public interface LoginService {
Credentials login(String username, String password)
throws IncorrectUsernameOrPasswordException;
throws IncorrectUsernameOrPasswordException, AuthenticationServiceUnavailableException;

default void logout() {
AuthorizationHolder.clear();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package pl.vtt.wpi.core.application.service;

import pl.vtt.wpi.core.application.exception.NetworkConfigurationException;
import pl.vtt.wpi.core.domain.model.device.AccessPointConfig;
import pl.vtt.wpi.core.domain.model.device.WifiConfig;

public interface NetworkConfigurationService {
void config(WifiConfig config);
void config(AccessPointConfig config);
void config(WifiConfig config) throws NetworkConfigurationException;
void config(AccessPointConfig config) throws NetworkConfigurationException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.PixelProgramNotFoundException;
import pl.vtt.wpi.core.application.exception.PixelProgramOperationException;
import pl.vtt.wpi.core.domain.model.color.RgbColor;
import pl.vtt.wpi.core.domain.model.device.PixelProgram;

import java.util.List;

public interface PixelProgramService {
List<PixelProgram> getAll();
void insert(List<PixelProgram> pixelPrograms);
List<PixelProgram> getAll() throws PixelProgramOperationException;
void insert(List<PixelProgram> pixelPrograms) throws PixelProgramOperationException;
void set(List<PixelProgram> pixelPrograms)
throws DataInconsistencyException;

PixelProgram get(int index)
throws PixelProgramNotFoundException;
throws PixelProgramNotFoundException, PixelProgramOperationException;
PixelProgram singleton(List<RgbColor> pixelProgram);
PixelProgram save(List<RgbColor> pixelProgram);
PixelProgram save(List<RgbColor> pixelProgram) throws PixelProgramOperationException;
PixelProgram update(int index, List<RgbColor> pixelProgram)
throws PixelProgramNotFoundException;
throws PixelProgramNotFoundException, PixelProgramOperationException;
PixelProgram remove(int index)
throws PixelProgramNotFoundException, DataInconsistencyException;
throws PixelProgramNotFoundException, DataInconsistencyException, PixelProgramOperationException;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package pl.vtt.wpi.core.application.service;

import pl.vtt.wpi.core.application.exception.DeviceRebootException;

public interface RebootService {
void reboot();
void reboot() throws DeviceRebootException;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package pl.vtt.wpi.core.application.service;

import pl.vtt.wpi.core.application.exception.DataInconsistencyException;
import pl.vtt.wpi.core.application.exception.RuntimeDataOperationException;
import pl.vtt.wpi.core.domain.model.device.RuntimeData;

public interface RuntimeDataService {
RuntimeData read();
void set(RuntimeData runtimeData) throws DataInconsistencyException;
void update(RuntimeData runtimeData) throws DataInconsistencyException;
RuntimeData read() throws RuntimeDataOperationException;
void set(RuntimeData runtimeData) throws DataInconsistencyException, RuntimeDataOperationException;
}
Loading
Loading