Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Welcome to the Licensing Project!

[![Build](https://github.com/bsayli/licensing/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bsayli/licensing/actions/workflows/build.yml)
[![Release](https://img.shields.io/github/v/release/bsayli/licensing?logo=github\&label=release)](https://github.com/bsayli/licensing/releases/latest)
[![Release](https://img.shields.io/github/v/release/bsayli/licensing?logo=github&label=release)](https://github.com/bsayli/licensing/releases/latest)
[![codecov](https://codecov.io/gh/bsayli/licensing/branch/main/graph/badge.svg)](https://codecov.io/gh/bsayli/licensing)

[![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen?logo=springboot)](https://spring.io/projects/spring-boot)
[![OpenAPI Generics](https://img.shields.io/badge/OpenAPI%20Generics-1.0.2-blue)](https://github.com/blueprint-platform/openapi-generics)
[![OpenAPI Generator](https://img.shields.io/badge/OpenAPI%20Generator-7.22.0-blue?logo=openapiinitiative)](https://openapi-generator.tech/)

[![Keycloak](https://img.shields.io/badge/Keycloak-26.x-purple?logo=keycloak)](https://www.keycloak.org/)
[![Redis](https://img.shields.io/badge/Redis-8.x-red?logo=redis)](https://redis.io/)
[![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/)
[![Docker](https://img.shields.io/badge/Docker-Compose-blue?logo=docker)](https://www.docker.com/)
[![Caffeine](https://img.shields.io/badge/Caffeine-Cache-orange)](https://github.com/ben-manes/caffeine)
[![JWT](https://img.shields.io/badge/JWT-EdDSA-lightgrey?logo=jsonwebtokens)](https://jwt.io/)

[![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/)
[![Docker](https://img.shields.io/badge/Docker-Compose-blue?logo=docker)](https://www.docker.com/)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)

<p align="center">
Expand Down Expand Up @@ -66,6 +71,7 @@ This project provides a **complete licensing framework** for applications, combi

## Subprojects

* **licensing-api-contract**: Shared API response contract (`ApiResponse<T>`, `ApiError`) reused by service, client, SDK, and CLI modules.
* **license-generator**: Java project for license key generation, encryption, and cryptographic tooling.
* **licensing-service**: Spring Boot application that issues and validates license tokens.
* **licensing-service-sdk**: Spring Boot application acting as a client SDK (with caching & detached signature) for integrating licensing capabilities into external apps.
Expand Down Expand Up @@ -136,7 +142,7 @@ licensing-service-sdk-cli | INFO Message: License is valid
cd licensing/licensing-service-sdk-cli
mvn clean package
cd target
java -jar licensing-service-sdk-cli-1.0.1.jar -s crm -v 1.5.0 -i "crm~macbook~00:2A:8D:BE:F1:56" -k "BSAYLI.<opaqueB64Url>"
java -jar licensing-service-sdk-cli-1.0.5.jar -s crm -v 1.5.0 -i "crm~macbook~00:2A:8D:BE:F1:56" -k "BSAYLI.<opaqueB64Url>"
```

---
Expand Down Expand Up @@ -183,9 +189,10 @@ If you found this project useful, please consider giving it a star ⭐ on GitHub

## Related Modules (Quick View)

| Module | Purpose | Documentation |
| ----------------------------- | ------------------------------------------ | ---------------------------------------------------------- |
| **licensing-service** | REST API for issuing and validating tokens | [README](licensing-service/README.md) |
| **licensing-service-sdk** | Client SDK for integration | [README](licensing-service-sdk/README.md) |
| **licensing-service-sdk-cli** | CLI demo client | [README](licensing-service-sdk-cli/README.md) |
| **license-generator** | Key & signature tooling | [README](license-generator/README.md) |
| Module | Purpose | Documentation |
|-------------------------------|-------------------------------------------------------------|-----------------------------------------------|
| **licensing-service** | REST API for issuing and validating tokens | [README](licensing-service/README.md) |
| **licensing-service-sdk** | Client SDK for integration | [README](licensing-service-sdk/README.md) |
| **licensing-service-sdk-cli** | CLI demo client | [README](licensing-service-sdk-cli/README.md) |
| **licensing-api-contract** | Shared API response contract (`ApiResponse<T>`, `ApiError`) | [README](licensing-api-contract/README.md) |
| **license-generator** | Key and signature tooling | [README](license-generator/README.md) |
4 changes: 2 additions & 2 deletions docker-compose/client/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ networks:
services:
licensing-service-sdk-cli:
build: ./../../licensing-service-sdk-cli
image: licensing-service-sdk-cli:1.0.1
image: licensing-service-sdk-cli:1.0.5
container_name: licensing-service-sdk-cli
networks:
- license_network
Expand All @@ -14,5 +14,5 @@ services:
INSTANCE_ID: "licensing-service~demo~00:11:22:33:44:55"
SERVICE_ID: "crm"
SERVICE_VERSION: "1.5.0"
LICENSE_KEY: "BSAYLI~X66e_qYlfPxWiIaN2ahPb9tQFyqjMuTih06LCytzjZ0~0aT6lLTZGkO1zHHPHFDzwF7zPiZLRLWSl06HSVQO5z+NqtzzcFCUkkVFuqHTYKcAcI9037sQQQSfBQakQDUoCA=="
LICENSE_KEY: "BSAYLI.AQA3-gCQ66DQsfC0LwnAO8DTjKgad7DhPaOtCf7WG2bFUoK9pmScIhhCf2S-D0j8g4jC7nlFrLDpuM0ezEoDQc79SizxxEIUGN9YUhTNBW7iRQ"
restart: "no"
7 changes: 4 additions & 3 deletions docker-compose/server/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ services:
restart: unless-stopped

licensing-service:
build: ./../../licensing-service
image: licensing-service:1.0.1
build:
context: ./../..
dockerfile: licensing-service/Dockerfile
container_name: licensing-service
networks: [ license_network ]
depends_on:
Expand Down Expand Up @@ -80,7 +81,7 @@ services:
build:
context: ./../..
dockerfile: licensing-service-sdk/Dockerfile
image: licensing-service-sdk:1.0.1
image: licensing-service-sdk:1.0.5
container_name: licensing-service-sdk
networks: [ license_network ]
depends_on:
Expand Down
4 changes: 2 additions & 2 deletions license-generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[![Build](https://github.com/bsayli/licensing/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bsayli/licensing/actions/workflows/build.yml)
[![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/)
[![JJWT](https://img.shields.io/badge/JJWT-0.12.x-orange)](https://github.com/jwtk/jjwt)
[![BouncyCastle](https://img.shields.io/badge/BouncyCastle-1.81-blue)](https://www.bouncycastle.org/)
[![Jackson](https://img.shields.io/badge/Jackson-2.19.x-lightgrey)](https://github.com/FasterXML/jackson)
[![BouncyCastle](https://img.shields.io/badge/BouncyCastle-1.84-blue)](https://www.bouncycastle.org/)
[![Jackson](https://img.shields.io/badge/Jackson-2.21.x-lightgrey)](https://github.com/FasterXML/jackson)
[![License](https://img.shields.io/badge/license-MIT-green)](../LICENSE)

Tools and libraries for generating cryptographic keys, constructing license strings, creating **detached Ed25519
Expand Down
10 changes: 5 additions & 5 deletions license-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@

<groupId>io.github.bsayli</groupId>
<artifactId>license-generator</artifactId>
<version>1.0.4</version>
<version>1.0.5</version>

<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jjwt.version>0.12.7</jjwt.version>
<bcprov-jdk18on.version>1.81</bcprov-jdk18on.version>
<jackson-databind.version>2.19.2</jackson-databind.version>
<logback-classic.version>1.5.18</logback-classic.version>
<bcprov-jdk18on.version>1.84</bcprov-jdk18on.version>
<jackson-databind.version>2.21.3</jackson-databind.version>
<logback-classic.version>1.5.32</logback-classic.version>
<slf4j-api.version>2.0.17</slf4j-api.version>
<junit.jupiter.version>5.13.4</junit.jupiter.version>
<jacoco-maven-plugin.version>0.8.13</jacoco-maven-plugin.version>
<jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>
<system-lambda.version>1.2.1</system-lambda.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.5.4</maven-surefire-plugin.version>
Expand Down
73 changes: 73 additions & 0 deletions licensing-api-contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Licensing API Contract

Shared API contract types used by the licensing modules.

This module contains the response envelope used by `licensing-service`, `licensing-service-client`, `licensing-service-sdk` and `licensing-service-sdk-cli` to ensure a consistent API contract across all layers.

```java
ApiResponse<T>
ApiError
```

## Purpose

`licensing-api-contract` keeps the API response shape in one place so server and client modules use the same contract instead of redefining response wrappers independently.

It is also used as a BYOE (Bring Your Own Envelope) example for `openapi-generics`: generated client response wrappers extend the existing `ApiResponse<T>` type instead of generating a new envelope model.

Example generated client wrapper:

```java
public class ApiResponseLicenseAccessResponse
extends ApiResponse<LicenseAccessResponse> {
}
```

## Contract Shape

`ApiResponse<T>` represents the standard licensing API envelope:

```json
{
"status": 200,
"message": "License is valid",
"data": {},
"errors": []
}
```

Fields:

| Field | Type | Description |
| --------- | ---------------- | ----------------------------------------------- |
| `status` | `int` | HTTP-style status code returned in the envelope |
| `message` | `String` | Human-readable response message |
| `data` | `T` | Typed response payload |
| `errors` | `List<ApiError>` | Structured error details |

## BYOE Requirements

Because generated wrappers extend `ApiResponse<T>`, the envelope is intentionally subclass-friendly:

* public no-argument constructor
* public getters and setters
* generic payload accessor through `getData()` / `setData(T data)`

This keeps the envelope compatible with generated client wrappers while preserving the original API contract.

## Module Coordinates

```xml
<dependency>
<groupId>io.github.bsayli</groupId>
<artifactId>licensing-api-contract</artifactId>
<version>1.0.5</version>
</dependency>
```

## See Also

* [`licensing-service`](../licensing-service/README.md) — REST API that returns `ApiResponse<T>`
* [`licensing-service-client`](../licensing-service-client/README.md) — generated client using the shared envelope
* [`licensing-service-sdk`](../licensing-service-sdk/README.md) — SDK layer built on top of the generated client
* [`licensing-service-sdk-cli`](../licensing-service-sdk-cli/README.md) — command-line client demo
36 changes: 36 additions & 0 deletions licensing-api-contract/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>io.github.bsayli</groupId>
<artifactId>licensing-api-contract</artifactId>
<version>1.0.5</version>
<packaging>jar</packaging>

<name>licensing-api-contract</name>
<description>API contract types used by the licensing service.</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<maven.compiler.release>21</maven.compiler.release>
<maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>${maven.compiler.release}</release>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.bsayli.licensing.common.api;
package io.github.bsayli.licensing.contract.api;

public record ApiError(String errorCode, String message) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package io.github.bsayli.licensing.contract.api;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class ApiResponse<T> {

private int status;
private String message;
private T data;
private List<ApiError> errors = Collections.emptyList();

public ApiResponse() {}

public ApiResponse(int status, String message, T data, List<ApiError> errors) {
this.status = status;
this.message = message;
this.data = data;
this.errors = errors != null ? errors : Collections.emptyList();
}

public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(200, "OK", data, Collections.emptyList());
}

public static <T> ApiResponse<T> of(int status, String message, T data) {
return new ApiResponse<>(status, message, data, Collections.emptyList());
}

public static <T> ApiResponse<T> error(int status, String message) {
return new ApiResponse<>(status, message, null, Collections.emptyList());
}

public static <T> ApiResponse<T> error(int status, String message, List<ApiError> errors) {
return new ApiResponse<>(status, message, null, errors);
}

public int getStatus() {
return status;
}

public void setStatus(int status) {
this.status = status;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

public List<ApiError> getErrors() {
return errors;
}

public void setErrors(List<ApiError> errors) {
this.errors = errors != null ? errors : Collections.emptyList();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ApiResponse<?> that)) return false;
return status == that.status
&& Objects.equals(message, that.message)
&& Objects.equals(data, that.data)
&& Objects.equals(errors, that.errors);
}

@Override
public int hashCode() {
return Objects.hash(status, message, data, errors);
}

@Override
public String toString() {
return "ApiResponse{"
+ "status="
+ status
+ ", message='"
+ message
+ '\''
+ ", data="
+ data
+ ", errors="
+ errors
+ '}';
}
}
8 changes: 5 additions & 3 deletions licensing-service-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

[![Build](https://github.com/bsayli/licensing/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bsayli/licensing/actions/workflows/build.yml)
[![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/)
[![OpenAPI Generator](https://img.shields.io/badge/OpenAPI%20Generator-7.x-blue?logo=openapiinitiative)](https://openapi-generator.tech/)
[![OpenAPI Generator](https://img.shields.io/badge/OpenAPI%20Generator-7.22.0-blue?logo=openapiinitiative)](https://openapi-generator.tech/)
[![OpenAPI Generics](https://img.shields.io/badge/OpenAPI%20Generics-1.0.2-blue)](https://github.com/blueprint-platform/openapi-generics)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen?logo=springboot)](https://spring.io/projects/spring-boot)
[![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/)
[![License](https://img.shields.io/badge/license-MIT-green)](../LICENSE)

Expand Down Expand Up @@ -34,7 +36,7 @@ Add to your service's `pom.xml`:
<dependency>
<groupId>io.github.bsayli</groupId>
<artifactId>licensing-service-client</artifactId>
<version>0.1.0</version>
<version>1.0.5</version>
</dependency>
```

Expand Down Expand Up @@ -132,7 +134,7 @@ mvn clean package
Produces:

```
target/licensing-service-client-0.1.0.jar
target/licensing-service-client-1.0.5.jar
```

---
Expand Down
Loading
Loading