Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
56ddbe5
Replace SimpleQuery runtime paths with repository-backed resolution
eviltester Jul 6, 2026
3fa8431
Add ChallengerAuto environment matrix harness
eviltester Jul 7, 2026
ab3076f
Use repository-native query and export paths
eviltester Jul 7, 2026
bd2c2ef
Move population import paths onto repositories
eviltester Jul 7, 2026
2f41878
Remove internal legacy repository escape usage
eviltester Jul 7, 2026
e7f84ce
Tighten repository compatibility boundaries
eviltester Jul 7, 2026
6bdd458
Replace legacy query paths with repository API
eviltester Jul 8, 2026
2a6f4aa
Remove remaining deprecated APIs
eviltester Jul 8, 2026
e153c7f
Tighten repository migration cleanup
eviltester Jul 8, 2026
d408b01
Configure simulation routes repository backend
eviltester Jul 9, 2026
d7776bd
Organize repository implementations by backend
eviltester Jul 9, 2026
4d3d1c6
Move entity instance writes behind repository drafts
eviltester Jul 9, 2026
7fae57c
Add Spotless formatting enforcement
eviltester Jul 9, 2026
4b962bc
Move in-memory stores into repository package
eviltester Jul 9, 2026
424cd19
Replace EntityInstance mutation access with repository wrapper
eviltester Jul 10, 2026
33cef15
test at lower abstractions
eviltester Jul 10, 2026
412f304
Extract shared relationship rules
eviltester Jul 11, 2026
67b82fc
Split repository API into focused store ports
eviltester Jul 11, 2026
10e2dc4
Keep relationship cascades inside repositories
eviltester Jul 11, 2026
922e3eb
Fix relationship rollback consistency
eviltester Jul 13, 2026
bd66c03
Add command services and route-free repository queries
eviltester Jul 13, 2026
6943046
Clean Spark internal HTTP boundary
eviltester Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 20 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh

set -e

run_maven() {
shell_name="$(uname -s 2>/dev/null || echo unknown)"

if [ -x "./mvnw" ]; then
"./mvnw" "$@"
elif [ -f "./mvnw.cmd" ] && case "$shell_name" in MINGW*|MSYS*|CYGWIN*) true;; *) false;; esac; then
./mvnw.cmd "$@"
elif command -v mvn.cmd >/dev/null 2>&1 && case "$shell_name" in MINGW*|MSYS*|CYGWIN*) true;; *) false;; esac; then
mvn.cmd "$@"
else
mvn "$@"
fi
}

echo "Running Spotless formatting check..."
run_maven -q spotless:check
10 changes: 7 additions & 3 deletions .github/workflows/run-juit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ jobs:
java: [ 17, 21 ]
name: Java ${{ matrix.java }} execution
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v1
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven
- name: Check Java formatting
run: mvn -B spotless:check
- name: Build with Maven
run: mvn clean test
run: mvn -B clean test
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import uk.co.compendiumdev.thingifier.Thingifier;
import uk.co.compendiumdev.thingifier.application.MainImplementation;
import uk.co.compendiumdev.thingifier.application.httprouting.ThingifierHttpApiRoutings;
import uk.co.compendiumdev.thingifier.core.repository.ThingRepositoryProviderConfig;
import uk.co.compendiumdev.thingifier.core.repository.ThingStoreProviderConfig;

public class ChallengeMain {

Expand All @@ -20,7 +20,7 @@ public static void main(String[] args) {
logger.info("Starting Challenger");

MainImplementation app = new MainImplementation();
ThingRepositoryProviderConfig repositoryConfig = ThingRepositoryProviderConfig.fromArgs(args);
ThingStoreProviderConfig repositoryConfig = ThingStoreProviderConfig.fromArgs(args);
logger.info("Using Thingifier repository {}", repositoryConfig.describe());
Thingifier thingifier = new ChallengeApiModel().get(repositoryConfig.createProvider());
app.registerModel("challengeapi", thingifier);
Expand All @@ -33,12 +33,17 @@ public static void main(String[] args) {

new IndexNowRouteHandler().configureRoutes();

// create a default configuration class which we pass into the ChallengeRouteHandler and configure via args
// create a default configuration class which we pass into the ChallengeRouteHandler and
// configure via args
ChallengerConfig config = new ChallengerConfig();
config.setSimulationRepositoryFromArgs(args);
logger.info(
"Using Simulation repository {}",
config.getSimulationRepositoryConfig().describe());

for (String arg : args) {
if (arg.toLowerCase().startsWith("-multiplayer") ||
arg.toLowerCase().startsWith("-multiuser")) {
if (arg.toLowerCase().startsWith("-multiplayer")
|| arg.toLowerCase().startsWith("-multiuser")) {
logger.info("Running in multiplayer mode");
config.setToMultiPlayerMode();
}
Expand All @@ -48,14 +53,13 @@ public static void main(String[] args) {
config.setToCloudPersistenceMode();
}

if(arg.toLowerCase().startsWith("-guikeepalive")){
if (arg.toLowerCase().startsWith("-guikeepalive")) {
logger.info("Setting GUI to keep session alive through XHR");
config.setGuiToKeepSessionAlive();
}

if (arg.toLowerCase().startsWith("-memory") ||
arg.toLowerCase().startsWith("-nostorage")
) {
if (arg.toLowerCase().startsWith("-memory")
|| arg.toLowerCase().startsWith("-nostorage")) {
logger.info("Setting persistence mechanism to no persistence");
config.setToNoPersistenceMode();
}
Expand All @@ -65,7 +69,7 @@ public static void main(String[] args) {
config.enableAdminApi();
}

if(arg.toLowerCase().startsWith("-unlimitedtodos")){
if (arg.toLowerCase().startsWith("-unlimitedtodos")) {
// remove the limit on number of todos
logger.info("Enabling Unlimited TODO Instances");
thingifier.getDefinitionNamed("todo").setNoMaxInstanceLimit();
Expand All @@ -78,48 +82,48 @@ public static void main(String[] args) {
// setup routes required for challenges
challenger = new ChallengeRouteHandler(thingifier, app.getApiDefn(), config);


app.chooseThingifier();
// can set profile by adding more configs, or just
// app.setProfileToUse(aProfile)
app.configureThingifierWithProfile();

app.setupDefaultGui();
app.getGuiManagement().setCanonicalHost("https://apichallenges.eviltester.com");
app.getGuiManagement().appendToCustomHeadContent(
"""
app.getGuiManagement()
.appendToCustomHeadContent(
"""
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/favicon/site.webmanifest">
<link rel="stylesheet" href="/css/toc.css">
<link rel="stylesheet" href="/css/content.css">
"""
);
""");

challenger.setupGui(app.getGuiManagement());
challenger.configureRoutes();

if(challenger.isSinglePlayerMode()){
if (challenger.isSinglePlayerMode()) {
logger.info("Running in Single User Mode");
challenger.getThingifier().ensureCreatedAndPopulatedInstanceDatabaseNamed(Challengers.SINGLE_PLAYER_GUID);
challenger
.getThingifier()
.ensureCreatedAndPopulatedInstanceDatabaseNamed(Challengers.SINGLE_PLAYER_GUID);
}

final ThingifierHttpApiRoutings restServer = app.startRestServer();

app.addBuiltInArgConfiguredHooks();

challenger.addHooks(restServer);

}

public static ChallengeRouteHandler getChallenger(){
public static ChallengeRouteHandler getChallenger() {
return challenger;
}

public static void stop(){
public static void stop() {
if (challenger != null) {
challenger.getThingifier().close();
challenger.close();
}
challenger = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,36 @@
import uk.co.compendiumdev.thingifier.application.httprouting.ThingifierHttpApiRoutings;
import uk.co.compendiumdev.thingifier.htmlgui.htmlgen.DefaultGUIHTML;


public class ChallengeRouteHandler {
private final Thingifier thingifier;
//List<RoutingDefinition> routes;
// List<RoutingDefinition> routes;

ThingifierApiDocumentationDefn apiChallengesDocumentationDefn;
ThingifierApiDocumentationDefn mirrorModeDocumentationDefn;

ChallengeDefinitions challengeDefinitions;
Challengers challengers;
private boolean single_player_mode;
private final ChallengerConfig config;
PersistenceLayer persistenceLayer;
private boolean guiStayAlive=false; // when set gui makes a call every 5 mins to keep session alive,
// when set gui makes a call every 5 mins to keep session alive
private boolean guiStayAlive;
private DefaultGUIHTML guiTemplates;
private SimulationRoutes simulationRoutes;

// not needed when storing data

public ChallengeRouteHandler(Thingifier thingifier, ThingifierApiDocumentationDefn apiDefn, ChallengerConfig config){
public ChallengeRouteHandler(
Thingifier thingifier,
ThingifierApiDocumentationDefn apiDefn,
ChallengerConfig config) {

this.config = config;
this.apiChallengesDocumentationDefn = apiDefn;
apiDefn.setThingifier(thingifier);
apiDefn.setSeoTitle("API Challenges API Documentation | API Challenges");
apiDefn.setSeoDescription("Explore API Challenges endpoint documentation with request formats, payload examples, and expected responses for practical API testing.");
apiDefn.setSeoDescription(
"Explore API Challenges endpoint documentation with request formats, payload examples, and expected responses for practical API testing.");
apiDefn.setMetaRobots("index,follow");
apiDefn.setOgType("website");
apiDefn.setTwitterCard("summary_large_image");
Expand All @@ -44,11 +52,13 @@ public ChallengeRouteHandler(Thingifier thingifier, ThingifierApiDocumentationDe
mirrorModeDocumentationDefn.setTitle("Mirror Mode");
mirrorModeDocumentationDefn.setDescription("Mirror HTTP Requests");
mirrorModeDocumentationDefn.setSeoTitle("Mirror Mode API Documentation | API Challenges");
mirrorModeDocumentationDefn.setSeoDescription("Review Mirror Mode endpoint documentation to inspect reflected HTTP requests, headers, and payload behavior for debugging and learning.");
mirrorModeDocumentationDefn.setSeoDescription(
"Review Mirror Mode endpoint documentation to inspect reflected HTTP requests, headers, and payload behavior for debugging and learning.");
mirrorModeDocumentationDefn.setMetaRobots("noindex,follow");
mirrorModeDocumentationDefn.setOgType("website");
mirrorModeDocumentationDefn.setTwitterCard("summary_large_image");
mirrorModeDocumentationDefn.addServer("https://apichallenges.eviltester.com", "cloud hosted version");
mirrorModeDocumentationDefn.addServer(
"https://apichallenges.eviltester.com", "cloud hosted version");
mirrorModeDocumentationDefn.addServer("http://localhost:4567", "local execution");
mirrorModeDocumentationDefn.setVersion("1.0.0");

Expand All @@ -58,48 +68,62 @@ public ChallengeRouteHandler(Thingifier thingifier, ThingifierApiDocumentationDe

single_player_mode = config.single_player_mode;
persistenceLayer = config.persistenceLayer;
guiStayAlive=config.guiStayAlive;
guiStayAlive = config.guiStayAlive;

challengeDefinitions = new ChallengeDefinitions(config);
this.thingifier = thingifier;
challengers = new Challengers(thingifier.getERmodel(), challengeDefinitions.getDefinedChallenges());
challengers =
new Challengers(
thingifier.getERmodel(), challengeDefinitions.getDefinedChallenges());
challengers.setPersistenceLayer(persistenceLayer);
if(!single_player_mode){
if (!single_player_mode) {
challengers.setMultiPlayerMode();
}

if(single_player_mode) {
if (single_player_mode) {
// auto load any single player challenger details in single player mode
persistenceLayer.tryToLoadChallenger(challengers, challengers.SINGLE_PLAYER_GUID);
}

challengers.setApiConfig(thingifier.apiConfig());

if(config.isAdminApiEnabled){
if (config.isAdminApiEnabled) {
enableAdminApi();
}

this.guiTemplates = new DefaultGUIHTML();


}

public boolean isSinglePlayerMode(){
public boolean isSinglePlayerMode() {
return single_player_mode;
}

public ChallengeRouteHandler configureRoutes() {

new ChallengerTrackingRoutes().configure(challengers, single_player_mode, apiChallengesDocumentationDefn, persistenceLayer, thingifier, challengeDefinitions);
new ChallengesRoutes().configure(challengers, single_player_mode, apiChallengesDocumentationDefn, challengeDefinitions);
new ChallengerTrackingRoutes()
.configure(
challengers,
single_player_mode,
apiChallengesDocumentationDefn,
persistenceLayer,
thingifier,
challengeDefinitions);
new ChallengesRoutes()
.configure(
challengers,
single_player_mode,
apiChallengesDocumentationDefn,
challengeDefinitions);
new HeartBeatRoutes().configure(apiChallengesDocumentationDefn);
new AuthRoutes().configure(challengers, apiChallengesDocumentationDefn);

// Mirror routes should not show up in the apichallenges apiDefn
new MirrorRoutes().configure(mirrorModeDocumentationDefn, guiTemplates);

// Simulation routes should not show
new SimulationRoutes(guiTemplates).configure();
simulationRoutes =
new SimulationRoutes(guiTemplates, config.getSimulationRepositoryConfig());
simulationRoutes.configure();

new SimpleApiRoutes(guiTemplates).configure();

Expand All @@ -111,28 +135,39 @@ public void addHooks(final ThingifierHttpApiRoutings restServer) {
// TODO: this is wrong - rethink this - we need SparkLevel, InternalHttp level (pre-post
// these hooks are registered at a spark before and after level so run on every request,
// regardless of thingifier used - this is wrong
restServer.registerInternalHttpResponseHook(new ChallengerInternalHTTPResponseHook(challengers));
restServer.registerInternalHttpRequestHook(new ChallengerInternalHTTPRequestHook(challengers));
restServer.registerInternalHttpResponseHook(
new ChallengerInternalHTTPResponseHook(challengers));
restServer.registerInternalHttpRequestHook(
new ChallengerInternalHTTPRequestHook(challengers));

// add hooks at the API bridge pre and post level so they only work in the specific thingifier
// add hooks at the API bridge pre and post level so they only work in the specific
// thingifier
restServer.registerHttpApiRequestHook(new ChallengerApiRequestHook(challengers));
restServer.registerHttpApiResponseHook(new ChallengerApiResponseHook(challengers, thingifier));
restServer.registerHttpApiResponseHook(
new ChallengerApiResponseHook(challengers, thingifier));
}

public void setupGui(DefaultGUIHTML guiManagement) {
this.guiTemplates = guiManagement;
new ChallengerWebGUI(guiManagement, guiStayAlive).setup(challengers, challengeDefinitions,
persistenceLayer, single_player_mode);
new ChallengerWebGUI(guiManagement, guiStayAlive)
.setup(challengers, challengeDefinitions, persistenceLayer, single_player_mode);
}

public Challengers getChallengers(){
public Challengers getChallengers() {
return challengers;
}

public Thingifier getThingifier() {
return thingifier;
}

public void close() {
if (simulationRoutes != null) {
simulationRoutes.close();
}
thingifier.close();
}

private void enableAdminApi() {
thingifier.apiConfig().adminConfig().enableAdminSearch();
thingifier.apiConfig().adminConfig().enableAdminDataClear();
Expand Down
Loading
Loading