diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100755
index 00000000..7bb71670
--- /dev/null
+++ b/.githooks/pre-commit
@@ -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
diff --git a/.github/workflows/run-juit-tests.yml b/.github/workflows/run-juit-tests.yml
index f4a74c50..4311b186 100644
--- a/.github/workflows/run-juit-tests.yml
+++ b/.github/workflows/run-juit-tests.yml
@@ -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
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java
index e3996999..53ebf4e3 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java
@@ -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 {
@@ -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);
@@ -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();
}
@@ -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();
}
@@ -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();
@@ -78,7 +82,6 @@ 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)
@@ -86,23 +89,25 @@ public static void main(String[] args) {
app.setupDefaultGui();
app.getGuiManagement().setCanonicalHost("https://apichallenges.eviltester.com");
- app.getGuiManagement().appendToCustomHeadContent(
-"""
+ app.getGuiManagement()
+ .appendToCustomHeadContent(
+ """
- """
- );
+ """);
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();
@@ -110,16 +115,15 @@ public static void main(String[] args) {
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;
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java
index c7b165a4..f6007cf3 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java
@@ -14,10 +14,9 @@
import uk.co.compendiumdev.thingifier.application.httprouting.ThingifierHttpApiRoutings;
import uk.co.compendiumdev.thingifier.htmlgui.htmlgen.DefaultGUIHTML;
-
public class ChallengeRouteHandler {
private final Thingifier thingifier;
- //List routes;
+ // List routes;
ThingifierApiDocumentationDefn apiChallengesDocumentationDefn;
ThingifierApiDocumentationDefn mirrorModeDocumentationDefn;
@@ -25,17 +24,26 @@ public class ChallengeRouteHandler {
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");
@@ -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");
@@ -58,40 +68,52 @@ 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);
@@ -99,7 +121,9 @@ public ChallengeRouteHandler configureRoutes() {
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();
@@ -111,21 +135,25 @@ 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;
}
@@ -133,6 +161,13 @@ 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();
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengerAuthData.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengerAuthData.java
index 0afa4944..d729ea8c 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengerAuthData.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengerAuthData.java
@@ -2,12 +2,11 @@
import com.google.gson.Gson;
import com.google.gson.JsonElement;
-import uk.co.compendiumdev.challenge.challengers.Challengers;
-
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
+import uk.co.compendiumdev.challenge.challengers.Challengers;
public class ChallengerAuthData {
@@ -21,7 +20,7 @@ public class ChallengerAuthData {
private ChallengerState state;
- public ChallengerAuthData(Collection definedChallenges){
+ public ChallengerAuthData(Collection definedChallenges) {
this.xChallenger = UUID.randomUUID().toString();
this.xAuthToken = UUID.randomUUID().toString();
this.expiresin = 600000; // 10 * 60 * 1000; // 10 minutes
@@ -35,7 +34,7 @@ public ChallengerAuthData(Collection definedChallenges){
private void resetChallengesStatus(Collection definedChallenges) {
challengeStatus = new HashMap<>();
// this should only be challenges defined
- for(CHALLENGE challenge : definedChallenges){
+ for (CHALLENGE challenge : definedChallenges) {
challengeStatus.put(challenge, false);
}
}
@@ -53,12 +52,12 @@ public String getNote() {
public void setNote(final String note) {
touch();
int maxLen = 100;
- if(note == null){
+ if (note == null) {
throw new RuntimeException("No Note Provided");
}
- if(note.length()<=maxLen){
- secretNote=note;
- }else {
+ if (note.length() <= maxLen) {
+ secretNote = note;
+ } else {
secretNote = note.substring(0, maxLen);
}
}
@@ -74,7 +73,7 @@ public long expiresAt() {
public void touch() {
lastAccessed = System.currentTimeMillis();
expiresin = expiresin + extratime;
- if(expiresin > 600000){
+ if (expiresin > 600000) {
expiresin = 600000;
}
}
@@ -85,7 +84,7 @@ public String getXChallenger() {
public Boolean statusOfChallenge(final CHALLENGE challenge) {
Boolean status = challengeStatus.get(challenge);
- if(status==null){
+ if (status == null) {
return false;
}
return challengeStatus.get(challenge);
@@ -93,7 +92,7 @@ public Boolean statusOfChallenge(final CHALLENGE challenge) {
public void pass(final CHALLENGE id) {
// only update challenge if configured for it
- if(challengeStatus.containsKey(id)){
+ if (challengeStatus.containsKey(id)) {
challengeStatus.put(id, true);
}
}
@@ -106,12 +105,13 @@ public void setState(ChallengerState challengerState) {
this.state = challengerState;
}
- public ChallengerAuthData fromData(ChallengerAuthData data, Collection definedChallenges){
+ public ChallengerAuthData fromData(
+ ChallengerAuthData data, Collection definedChallenges) {
// set from data but do not fully trust data
setNote(data.secretNote);
// only allow setting the uuid if we are not in single player mode
- if(!xChallenger.equals(Challengers.SINGLE_PLAYER_GUID)) {
+ if (!xChallenger.equals(Challengers.SINGLE_PLAYER_GUID)) {
try {
setXChallengerGUID(UUID.fromString(data.getXChallenger()).toString());
} catch (Exception e) {
@@ -121,7 +121,7 @@ public ChallengerAuthData fromData(ChallengerAuthData data, Collection getRouteRegistrar;
public IndexNowRouteHandler() {
- this(System.getenv(INDEX_NOW_KEY_LOCATION),
- System.getenv(INDEX_NOW_KEY),
- Spark::get);
+ this(System.getenv(INDEX_NOW_KEY_LOCATION), System.getenv(INDEX_NOW_KEY), Spark::get);
}
- IndexNowRouteHandler(final String keyLocation,
- final String key,
- final BiConsumer getRouteRegistrar) {
+ IndexNowRouteHandler(
+ final String keyLocation,
+ final String key,
+ final BiConsumer getRouteRegistrar) {
this.keyLocation = normalize(keyLocation);
this.key = normalize(key);
this.getRouteRegistrar = getRouteRegistrar;
@@ -35,16 +33,19 @@ public IndexNowRouteHandler() {
public boolean configureRoutes() {
if (!isConfigurationValid()) {
- logger.info("IndexNow endpoint not enabled because environment variables are missing or invalid");
+ logger.info(
+ "IndexNow endpoint not enabled because environment variables are missing or invalid");
return false;
}
- getRouteRegistrar.accept(keyLocation, (request, response) -> {
- if (response != null) {
- response.type("text/plain; charset=utf-8");
- }
- return key;
- });
+ getRouteRegistrar.accept(
+ keyLocation,
+ (request, response) -> {
+ if (response != null) {
+ response.type("text/plain; charset=utf-8");
+ }
+ return key;
+ });
logger.info("IndexNow endpoint enabled at {}", keyLocation);
return true;
@@ -63,11 +64,11 @@ static boolean isValidLocation(final String location) {
return false;
}
- return !location.contains("*") &&
- !location.contains("?") &&
- !location.contains("#") &&
- !location.contains(" ") &&
- !location.contains("\\");
+ return !location.contains("*")
+ && !location.contains("?")
+ && !location.contains("#")
+ && !location.contains(" ")
+ && !location.contains("\\");
}
private static String normalize(final String value) {
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeApiModel.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeApiModel.java
index 8f2836bc..70391f00 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeApiModel.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeApiModel.java
@@ -1,22 +1,22 @@
package uk.co.compendiumdev.challenge.apimodel;
+import static uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType.STRING;
+
+import uk.co.compendiumdev.thingifier.Thingifier;
import uk.co.compendiumdev.thingifier.core.EntityRelModel;
import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
-import uk.co.compendiumdev.thingifier.Thingifier;
-import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.definitions.validation.VRule;
-import uk.co.compendiumdev.thingifier.core.repository.ThingRepositoryProvider;
-
-import static uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType.STRING;
+import uk.co.compendiumdev.thingifier.core.repository.ThingStoreProvider;
public class ChallengeApiModel {
public Thingifier get() {
return get(new EntityRelModel());
}
- public Thingifier get(final ThingRepositoryProvider repositoryProvider) {
- return get(new EntityRelModel(repositoryProvider));
+ public Thingifier get(final ThingStoreProvider storeProvider) {
+ return get(new EntityRelModel(storeProvider));
}
public Thingifier get(final EntityRelModel entityRelModel) {
@@ -26,29 +26,24 @@ public Thingifier get(final EntityRelModel entityRelModel) {
para.append("A Simple todo list");
-
todoList.setDocumentation("Simple Todo List", para.toString());
// can create a maximum of 20 todos in the challenge todos list
EntityDefinition todo = todoList.defineThing("todo", "todos", 20);
todo.addAsPrimaryKeyField(Field.is("id", FieldType.AUTO_INCREMENT));
todo.addFields(
- Field.is("title", STRING).
- withExample("A title").
- makeMandatory().
- withValidation(
- VRule.notEmpty(),
- VRule.maximumLength(50)).
- withDescription("The title of the TODO Item"),
- Field.is("doneStatus", FieldType.BOOLEAN).
- withDefaultValue("false").
- withDescription("Status to track the todo completion"),
- Field.is("description", STRING).
- withExample("my description").
- withValidation(VRule.maximumLength(200)).
- withDescription("Longer description of the todo")
- );
-
+ Field.is("title", STRING)
+ .withExample("A title")
+ .makeMandatory()
+ .withValidation(VRule.notEmpty(), VRule.maximumLength(50))
+ .withDescription("The title of the TODO Item"),
+ Field.is("doneStatus", FieldType.BOOLEAN)
+ .withDefaultValue("false")
+ .withDescription("Status to track the todo completion"),
+ Field.is("description", STRING)
+ .withExample("my description")
+ .withValidation(VRule.maximumLength(200))
+ .withDescription("Longer description of the todo"));
todoList.setDataGenerator(new TodoAPIDataPopulator());
todoList.generateData(EntityRelModel.DEFAULT_DATABASE_NAME);
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeThingifier.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeThingifier.java
index 95267f41..d078a928 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeThingifier.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/ChallengeThingifier.java
@@ -9,7 +9,8 @@
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
-import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceCollection;
+import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceDraft;
+import uk.co.compendiumdev.thingifier.core.repository.ThingStore;
public class ChallengeThingifier {
@@ -17,11 +18,11 @@ public class ChallengeThingifier {
public EntityDefinition challengeDefn;
private ChallengeDefinitions challengeDefinitions;
- public ChallengeThingifier(){
+ public ChallengeThingifier() {
createThingifier();
}
- private void createThingifier(){
+ private void createThingifier() {
// fake the data storage
this.challengeThingifier = new Thingifier();
@@ -31,58 +32,59 @@ private void createThingifier(){
this.challengeThingifier.apiConfig().setApiToAllowRobotsIndexingResponses(false);
this.challengeThingifier.apiConfig().setReturnSingleGetItemsAsCollection(true);
- this.challengeDefn = this.challengeThingifier.defineThing(
- "challenge", "challenges");
+ this.challengeDefn = this.challengeThingifier.defineThing("challenge", "challenges");
this.challengeDefn.addAsPrimaryKeyField(Field.is("id", FieldType.AUTO_INCREMENT));
this.challengeDefn.addFields(
-
- Field.is("name", FieldType.STRING).withDefaultValue("")
- .withDescription("title to describe the challenge"),
- Field.is("description", FieldType.STRING).withDefaultValue("")
+ Field.is("name", FieldType.STRING)
+ .withDefaultValue("")
+ .withDescription("title to describe the challenge"),
+ Field.is("description", FieldType.STRING)
+ .withDefaultValue("")
.withDescription("description of the challenge"),
- Field.is("status", FieldType.BOOLEAN).withDefaultValue("false")
- .withDescription("status to track if challenges is completed")
- );
+ Field.is("status", FieldType.BOOLEAN)
+ .withDefaultValue("false")
+ .withDescription("status to track if challenges is completed"));
}
public void populateThingifierFrom(ChallengeDefinitions challengeDefinitions) {
this.challengeDefinitions = challengeDefinitions;
// create all instances from the definitions, then when we want to
// set all the status codes to the specific challenger status
+ ThingStore store = challengeThingifier.getStore(EntityRelModel.DEFAULT_DATABASE_NAME);
for (ChallengeDefinitionData challenge : challengeDefinitions.getChallenges()) {
- EntityInstanceCollection instances = challengeThingifier.getThingInstancesNamed(challengeDefn.getName(), EntityRelModel.DEFAULT_DATABASE_NAME);
- createManagedInstance(instances).
- overrideValue("id", challenge.id).
- setValue("name", challenge.name).
- setValue("description", challenge.description);
+ store.entities()
+ .create(
+ EntityInstanceDraft.forEntity(challengeDefn)
+ .withProtectedField("id", challenge.id)
+ .withField("name", challenge.name)
+ .withField("description", challenge.description));
}
}
- private EntityInstance createManagedInstance(EntityInstanceCollection entityStorage) {
- EntityInstance instance = new EntityInstance(entityStorage.definition());
- entityStorage.addInstance(instance);
- return instance;
- }
-
- public void populateThingifierFromStatus(ChallengerAuthData challenger){
+ public void populateThingifierFromStatus(ChallengerAuthData challenger) {
ChallengerAuthData challengerToUse = challenger;
- if(challenger == null){
+ if (challenger == null) {
// create one just to show no progress
challengerToUse = new ChallengerAuthData(challengeDefinitions.getDefinedChallenges());
}
+ ThingStore store = challengeThingifier.getStore(EntityRelModel.DEFAULT_DATABASE_NAME);
for (ChallengeDefinitionData challenge : challengeDefinitions.getChallenges()) {
final EntityInstance instance =
- challengeThingifier.getThingInstancesNamed(challengeDefn.getName(), EntityRelModel.DEFAULT_DATABASE_NAME)
- .findInstanceByFieldNameAndValue("id", challenge.id);
- instance.setValue("status",
- challengerToUse.statusOfChallenge(
- challengeDefinitions.
- getChallenge(challenge.name)
- ).toString()
- );
+ store.entityQueries().findByField(challengeDefn, "id", challenge.id);
+ store.entities()
+ .patch(
+ instance,
+ EntityInstanceDraft.forEntity(challengeDefn)
+ .withField(
+ "status",
+ challengerToUse
+ .statusOfChallenge(
+ challengeDefinitions.getChallenge(
+ challenge.name))
+ .toString()));
}
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/TodoAPIDataPopulator.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/TodoAPIDataPopulator.java
index 40ffbb3e..4ce02e9d 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/TodoAPIDataPopulator.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/apimodel/TodoAPIDataPopulator.java
@@ -1,35 +1,33 @@
package uk.co.compendiumdev.challenge.apimodel;
-import uk.co.compendiumdev.thingifier.core.EntityRelModel;
+import uk.co.compendiumdev.thingifier.core.domain.datapopulator.RepositoryDataPopulator;
import uk.co.compendiumdev.thingifier.core.domain.definitions.ERSchema;
-import uk.co.compendiumdev.thingifier.core.domain.instances.ERInstanceData;
-import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
-import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceCollection;
-import uk.co.compendiumdev.thingifier.core.domain.datapopulator.DataPopulator;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
+import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceDraft;
+import uk.co.compendiumdev.thingifier.core.repository.ThingStore;
-public class TodoAPIDataPopulator implements DataPopulator {
+public class TodoAPIDataPopulator implements RepositoryDataPopulator {
@Override
- public void populate(final ERSchema schema, final ERInstanceData database) {
+ public void populate(final ERSchema schema, final ThingStore store) {
+ String[] todos = {
+ "scan paperwork",
+ "file paperwork",
+ "process payments",
+ "escalate late payments",
+ "pay invoices",
+ "process payroll",
+ "train staff",
+ "schedule meeting",
+ "tidy meeting room",
+ "install webcam"
+ };
- String [] todos={
- "scan paperwork",
- "file paperwork",
- "process payments",
- "escalate late payments",
- "pay invoices",
- "process payroll",
- "train staff",
- "schedule meeting",
- "tidy meeting room",
- "install webcam"};
+ EntityDefinition todo = schema.getEntityDefinitionNamed("todo");
- EntityInstanceCollection todo = database.getInstanceCollectionForEntityNamed("todo");
-
- for(String todoItem : todos){
- EntityInstance instance = new EntityInstance(todo.definition());
- todo.addInstance(instance);
- instance.setValue("title", todoItem);
+ for (String todoItem : todos) {
+ store.entities()
+ .create(EntityInstanceDraft.forEntity(todo).withField("title", todoItem));
}
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiRequestHook.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiRequestHook.java
index c488a202..9f3392fd 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiRequestHook.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiRequestHook.java
@@ -1,5 +1,8 @@
package uk.co.compendiumdev.challenge.challengehooks;
+import static uk.co.compendiumdev.thingifier.api.http.ThingifierHttpApi.HTTP_SESSION_HEADER_NAME;
+
+import java.util.List;
import uk.co.compendiumdev.challenge.CHALLENGE;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
import uk.co.compendiumdev.challenge.challengers.Challengers;
@@ -10,36 +13,35 @@
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiRequestHook;
-import java.util.List;
-
-import static uk.co.compendiumdev.thingifier.api.http.ThingifierHttpApi.HTTP_SESSION_HEADER_NAME;
-
public class ChallengerApiRequestHook implements HttpApiRequestHook {
private final Challengers challengers;
- public ChallengerApiRequestHook(Challengers challengers){
+ public ChallengerApiRequestHook(Challengers challengers) {
this.challengers = challengers;
}
@Override
public HttpApiResponse run(final HttpApiRequest request, final ThingifierApiConfig config) {
- ChallengerAuthData challenger = challengers.getChallenger(request.getHeader("X-CHALLENGER"));
- if(challenger==null){
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.getHeader("X-CHALLENGER"));
+ if (challenger == null) {
// if there is no x-challenger and we are in multi-player mode then do not allow any
// POST, DELETE, PUT, PATCH through to the API as this would amend the default database
- if(challengers.isMultiPlayerMode()){
- if(
- request.getVerb().equals(HttpApiRequest.VERB.POST) ||
- request.getVerb().equals(HttpApiRequest.VERB.PUT) ||
- request.getVerb().equals(HttpApiRequest.VERB.PATCH) ||
- request.getVerb().equals(HttpApiRequest.VERB.DELETE)
- ){
+ if (challengers.isMultiPlayerMode()) {
+ if (request.getVerb().equals(HttpApiRequest.VERB.POST)
+ || request.getVerb().equals(HttpApiRequest.VERB.PUT)
+ || request.getVerb().equals(HttpApiRequest.VERB.PATCH)
+ || request.getVerb().equals(HttpApiRequest.VERB.DELETE)) {
return new HttpApiResponse(
request.getHeaders(),
- new ApiResponse(401, true, List.of("Cannot amend details. Missing a valid X-CHALLENGER header.")),
+ new ApiResponse(
+ 401,
+ true,
+ List.of(
+ "Cannot amend details. Missing a valid X-CHALLENGER header.")),
new JsonThing(challengers.getApiConfig().jsonOutput()),
challengers.getApiConfig());
}
@@ -58,16 +60,16 @@ public HttpApiResponse run(final HttpApiRequest request, final ThingifierApiConf
// add challenger guid as session id to request
request.addHeader(HTTP_SESSION_HEADER_NAME, challenger.getXChallenger());
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().size()==0){
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().size() == 0) {
challengers.pass(challenger, CHALLENGE.GET_TODOS);
}
- if(request.getVerb() == HttpApiRequest.VERB.HEAD &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().size()==0){
- challengers.pass(challenger,CHALLENGE.GET_HEAD_TODOS);
+ if (request.getVerb() == HttpApiRequest.VERB.HEAD
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().size() == 0) {
+ challengers.pass(challenger, CHALLENGE.GET_HEAD_TODOS);
}
return null;
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiResponseHook.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiResponseHook.java
index e22d72df..a7ad40ca 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiResponseHook.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerApiResponseHook.java
@@ -1,21 +1,21 @@
package uk.co.compendiumdev.challenge.challengehooks;
+import java.util.Collection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.compendiumdev.challenge.CHALLENGE;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
import uk.co.compendiumdev.challenge.challengers.Challengers;
-import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceCollection;
import uk.co.compendiumdev.thingifier.Thingifier;
-import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.ContentTypeHeaderParser;
-import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.AcceptHeaderParser;
import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
import uk.co.compendiumdev.thingifier.api.http.HttpApiResponse;
+import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.AcceptHeaderParser;
+import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.ContentTypeHeaderParser;
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiResponseHook;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
-import java.util.Collection;
-
+import uk.co.compendiumdev.thingifier.core.repository.EntityInstanceQuery;
public class ChallengerApiResponseHook implements HttpApiResponseHook {
@@ -30,273 +30,306 @@ public ChallengerApiResponseHook(final Challengers challengers, Thingifier thing
}
@Override
- public HttpApiResponse run(final HttpApiRequest request,
- final HttpApiResponse response,
- final ThingifierApiConfig config) {
+ public HttpApiResponse run(
+ final HttpApiRequest request,
+ final HttpApiResponse response,
+ final ThingifierApiConfig config) {
- ChallengerAuthData challenger = challengers.getChallenger(request.getHeader("X-CHALLENGER"));
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.getHeader("X-CHALLENGER"));
- if(challenger==null){
+ if (challenger == null) {
// cannot track challenges
return null;
}
// READ
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().matches("todos/.*") &&
- response.getStatusCode()==200){
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 200) {
challengers.pass(challenger, CHALLENGE.GET_TODO);
}
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().matches("todos/.*") &&
- response.getStatusCode()==404){
- challengers.pass(challenger,CHALLENGE.GET_TODO_404);
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 404) {
+ challengers.pass(challenger, CHALLENGE.GET_TODO_404);
}
final AcceptHeaderParser acceptParser = new AcceptHeaderParser(request.getHeader("accept"));
- final ContentTypeHeaderParser contentTypeParser = new ContentTypeHeaderParser(request.getHeader("content-type"));
-
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().isEmpty() &&
- acceptParser.hasAskedForXML() &&
- response.getType().contentEquals("application/xml") &&
- response.getStatusCode()==200
- ){
- challengers.pass(challenger,CHALLENGE.GET_ACCEPT_XML);
+ final ContentTypeHeaderParser contentTypeParser =
+ new ContentTypeHeaderParser(request.getHeader("content-type"));
+
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().isEmpty()
+ && acceptParser.hasAskedForXML()
+ && response.getType().contentEquals("application/xml")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.GET_ACCEPT_XML);
}
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().isEmpty() &&
- acceptParser.hasAskedForJSON() &&
- response.getType().contentEquals("application/json") &&
- response.getStatusCode()==200
- ){
- challengers.pass(challenger,CHALLENGE.GET_ACCEPT_JSON);
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().isEmpty()
+ && acceptParser.hasAskedForJSON()
+ && response.getType().contentEquals("application/json")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.GET_ACCEPT_JSON);
}
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().isEmpty() &&
- acceptParser.missingAcceptHeader() &&
- response.getType().contentEquals("application/json") &&
- response.getStatusCode()==200
- ){
- challengers.pass(challenger,CHALLENGE.GET_JSON_BY_DEFAULT_NO_ACCEPT);
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().isEmpty()
+ && acceptParser.missingAcceptHeader()
+ && response.getType().contentEquals("application/json")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.GET_JSON_BY_DEFAULT_NO_ACCEPT);
}
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().isEmpty() &&
- !acceptParser.isSupportedHeader() &&
- response.getStatusCode()==406
- ){
- challengers.pass(challenger,CHALLENGE.GET_UNSUPPORTED_ACCEPT_406);
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().isEmpty()
+ && !acceptParser.isSupportedHeader()
+ && response.getStatusCode() == 406) {
+ challengers.pass(challenger, CHALLENGE.GET_UNSUPPORTED_ACCEPT_406);
}
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().isEmpty() &&
- acceptParser.hasAskedForANY() &&
- response.getType().contentEquals("application/json") &&
- response.getStatusCode()==200
- ){
- challengers.pass(challenger,CHALLENGE.GET_ACCEPT_ANY_DEFAULT_JSON);
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().isEmpty()
+ && acceptParser.hasAskedForANY()
+ && response.getType().contentEquals("application/json")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.GET_ACCEPT_ANY_DEFAULT_JSON);
}
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().isEmpty() &&
- acceptParser.hasAskedForXML() &&
- acceptParser.hasAskedForJSON() &&
- acceptParser.hasAPreferenceForXml() &&
- response.getType().contentEquals("application/xml") &&
- response.getStatusCode()==200
- ){
- challengers.pass(challenger,CHALLENGE.GET_ACCEPT_XML_PREFERRED);
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().isEmpty()
+ && acceptParser.hasAskedForXML()
+ && acceptParser.hasAskedForJSON()
+ && acceptParser.hasAPreferenceForXml()
+ && response.getType().contentEquals("application/xml")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.GET_ACCEPT_XML_PREFERRED);
}
-
- if(request.getVerb() == HttpApiRequest.VERB.GET &&
- request.getPath().contentEquals("todos") &&
- request.getQueryParams().containsKey("doneStatus") &&
- request.getQueryParams().get("doneStatus").contentEquals("true") &&
- response.getStatusCode()==200){
+ if (request.getVerb() == HttpApiRequest.VERB.GET
+ && request.getPath().contentEquals("todos")
+ && request.getQueryParams().containsKey("doneStatus")
+ && request.getQueryParams().get("doneStatus").contentEquals("true")
+ && response.getStatusCode() == 200) {
// only pass if there are done and not done todos
- final EntityInstanceCollection thing = thingifier.getThingInstancesNamed("todo", challenger.getXChallenger());
- final EntityInstance aDoneThing = thing.findInstanceByFieldNameAndValue("doneStatus", "true");
- final EntityInstance aNotDoneThing = thing.findInstanceByFieldNameAndValue("doneStatus", "false");
- if(aDoneThing!=null && aNotDoneThing!=null) {
- challengers.pass(challenger,CHALLENGE.GET_TODOS_FILTERED);
+ final EntityInstance aDoneThing = findTodoByField(challenger, "doneStatus", "true");
+ final EntityInstance aNotDoneThing = findTodoByField(challenger, "doneStatus", "false");
+ if (aDoneThing != null && aNotDoneThing != null) {
+ challengers.pass(challenger, CHALLENGE.GET_TODOS_FILTERED);
}
}
// CREATE
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- response.getStatusCode()==201){
- challengers.pass(challenger,CHALLENGE.POST_TODOS);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && response.getStatusCode() == 201) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos/.*") &&
- response.getStatusCode()==404){
- for(String errorMessage : response.apiResponse().getErrorMessages()){
- if(errorMessage.startsWith("No such todo entity instance with id ==")){
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 404) {
+ for (String errorMessage : response.apiResponse().getErrorMessages()) {
+ if (errorMessage.startsWith("No such todo entity instance with id ==")) {
challengers.pass(challenger, CHALLENGE.POST_TODOS_404);
}
}
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- response.getStatusCode()==201){
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && response.getStatusCode() == 201) {
try {
String location = response.getHeaders().get("Location");
String[] locationParts = location.split("/");
- if(locationParts.length>1){
+ if (locationParts.length > 2) {
// to check it is an int
- int todoId = Integer.parseInt(locationParts[2]);
- final EntityInstanceCollection thing = thingifier.getThingInstancesNamed("todo", challenger.getXChallenger());
- EntityInstance aTodo = thing.findInstanceByPrimaryKey(locationParts[2]);
- if(aTodo.getFieldValue("title").asString().length() == 50 &&
- aTodo.getFieldValue("description").asString().length() == 200
- ){
- challengers.pass(challenger, CHALLENGE.POST_MAX_OUT_TITLE_DESCRIPTION_LENGTH);
+ Integer.parseInt(locationParts[2]);
+ EntityInstance aTodo = findTodoByIdentifier(challenger, locationParts[2]);
+ if (aTodo != null
+ && aTodo.getFieldValue("title").asString().length() == 50
+ && aTodo.getFieldValue("description").asString().length() == 200) {
+ challengers.pass(
+ challenger, CHALLENGE.POST_MAX_OUT_TITLE_DESCRIPTION_LENGTH);
}
}
- }catch(Exception e){
- logger.warn("Error checking post todos 201 for max length ", e);
+ } catch (Exception e) {
+ logger.warn("Error checking post todos 201 for max length ", e);
}
-
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- contentTypeParser.isXML() &&
- response.getType().contentEquals("application/xml") &&
- response.getStatusCode()==201){
- challengers.pass(challenger,CHALLENGE.POST_CREATE_XML);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && contentTypeParser.isXML()
+ && response.getType().contentEquals("application/xml")
+ && response.getStatusCode() == 201) {
+ challengers.pass(challenger, CHALLENGE.POST_CREATE_XML);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- contentTypeParser.isJSON() &&
- acceptParser.hasAskedForJSON() &&
- response.getType().contentEquals("application/json") &&
- response.getStatusCode()==201){
- challengers.pass(challenger,CHALLENGE.POST_CREATE_JSON);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && contentTypeParser.isJSON()
+ && acceptParser.hasAskedForJSON()
+ && response.getType().contentEquals("application/json")
+ && response.getStatusCode() == 201) {
+ challengers.pass(challenger, CHALLENGE.POST_CREATE_JSON);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- contentTypeParser.isJSON() &&
- response.getType().contentEquals("application/xml") &&
- response.getStatusCode()==201){
- challengers.pass(challenger,CHALLENGE.POST_CREATE_JSON_ACCEPT_XML);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && contentTypeParser.isJSON()
+ && response.getType().contentEquals("application/xml")
+ && response.getStatusCode() == 201) {
+ challengers.pass(challenger, CHALLENGE.POST_CREATE_JSON_ACCEPT_XML);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- acceptParser.hasAskedForJSON() &&
- contentTypeParser.isXML() &&
- response.getType().contentEquals("application/json") &&
- response.getStatusCode()==201){
- challengers.pass(challenger,CHALLENGE.POST_CREATE_XML_ACCEPT_JSON);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && acceptParser.hasAskedForJSON()
+ && contentTypeParser.isXML()
+ && response.getType().contentEquals("application/json")
+ && response.getStatusCode() == 201) {
+ challengers.pass(challenger, CHALLENGE.POST_CREATE_XML_ACCEPT_JSON);
}
-
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- response.getStatusCode()==400 &&
- collate(response.apiResponse().getErrorMessages()).contains(
- "Failed Validation: doneStatus should be BOOLEAN")){
- challengers.pass(challenger,CHALLENGE.POST_TODOS_BAD_DONE_STATUS);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && response.getStatusCode() == 400
+ && collate(response.apiResponse().getErrorMessages())
+ .contains("Failed Validation: doneStatus should be BOOLEAN")) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS_BAD_DONE_STATUS);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ &&
// trap when creating or amending
- request.getPath().startsWith("todo") &&
- response.getStatusCode()==400 &&
- collate(response.apiResponse().getErrorMessages()).contains(
- "Failed Validation: Maximum allowable length exceeded for title - maximum allowed is 50")){
- challengers.pass(challenger,CHALLENGE.POST_TODOS_TOO_LONG_TITLE_LENGTH);
+ request.getPath().startsWith("todo")
+ && response.getStatusCode() == 400
+ && collate(response.apiResponse().getErrorMessages())
+ .contains(
+ "Failed Validation: Maximum allowable length exceeded for title - maximum allowed is 50")) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS_TOO_LONG_TITLE_LENGTH);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ &&
// trap when creating or amending
- request.getPath().startsWith("todo") &&
- response.getStatusCode()==400 &&
- collate(response.apiResponse().getErrorMessages()).contains(
- "Failed Validation: Maximum allowable length exceeded for description - maximum allowed is 200")){
- challengers.pass(challenger,CHALLENGE.POST_TODOS_TOO_LONG_DESCRIPTION_LENGTH);
+ request.getPath().startsWith("todo")
+ && response.getStatusCode() == 400
+ && collate(response.apiResponse().getErrorMessages())
+ .contains(
+ "Failed Validation: Maximum allowable length exceeded for description - maximum allowed is 200")) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS_TOO_LONG_DESCRIPTION_LENGTH);
}
// POST to create too many todos
- if (request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().contentEquals("todos") &&
- response.getStatusCode() == 400 &&
- collate(response.apiResponse().getErrorMessages()).contains("ERROR: Cannot add instance, maximum limit of 20 reached")
- ) {
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().contentEquals("todos")
+ && response.getStatusCode() == 400
+ && collate(response.apiResponse().getErrorMessages())
+ .contains("ERROR: Cannot add instance, maximum limit of 20 reached")) {
challengers.pass(challenger, CHALLENGE.POST_ALL_TODOS);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- response.getStatusCode()==415){
- challengers.pass(challenger,CHALLENGE.POST_TODOS_415);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && response.getStatusCode() == 415) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS_415);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- response.getStatusCode()==413 &&
- collate(response.apiResponse().getErrorMessages()).contains("Error: Request body too large, max allowed is 5000 bytes")
- ){
- challengers.pass(challenger,CHALLENGE.POST_TODOS_TOO_LONG_PAYLOAD_SIZE);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && response.getStatusCode() == 413
+ && collate(response.apiResponse().getErrorMessages())
+ .contains("Error: Request body too large, max allowed is 5000 bytes")) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS_TOO_LONG_PAYLOAD_SIZE);
}
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos") &&
- response.getStatusCode()==400 &&
- collate(response.apiResponse().getErrorMessages()).contains("Could not find field:")
- ){
- challengers.pass(challenger,CHALLENGE.POST_TODOS_INVALID_EXTRA_FIELD);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos")
+ && response.getStatusCode() == 400
+ && collate(response.apiResponse().getErrorMessages())
+ .contains("Could not find field:")) {
+ challengers.pass(challenger, CHALLENGE.POST_TODOS_INVALID_EXTRA_FIELD);
}
// UPDATE
- if(request.getVerb() == HttpApiRequest.VERB.POST &&
- request.getPath().matches("todos/.*") &&
- response.getStatusCode()==200){
- challengers.pass(challenger,CHALLENGE.POST_UPDATE_TODO);
+ if (request.getVerb() == HttpApiRequest.VERB.POST
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.POST_UPDATE_TODO);
}
-
// DELETE
- if(request.getVerb() == HttpApiRequest.VERB.DELETE &&
- request.getPath().matches("todos/.*") &&
- response.getStatusCode()==200){
- challengers.pass(challenger,CHALLENGE.DELETE_A_TODO);
+ if (request.getVerb() == HttpApiRequest.VERB.DELETE
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 200) {
+ challengers.pass(challenger, CHALLENGE.DELETE_A_TODO);
}
- if(request.getVerb() == HttpApiRequest.VERB.DELETE &&
- request.getPath().matches("todos/.*") &&
- response.getStatusCode()==200 &&
- thingifier.getThingInstancesNamed("todo", challenger.getXChallenger()).countInstances()==0){
- challengers.pass(challenger,CHALLENGE.DELETE_ALL_TODOS);
+ if (request.getVerb() == HttpApiRequest.VERB.DELETE
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 200
+ && countTodos(challenger) == 0) {
+ challengers.pass(challenger, CHALLENGE.DELETE_ALL_TODOS);
}
// do not interfere with api and return null
return null;
}
- String collate(Collection strings){
+ private EntityInstance findTodoByField(
+ final ChallengerAuthData challenger, final String fieldName, final String fieldValue) {
+ EntityDefinition todo = todoDefinition();
+ EntityInstanceQuery query = queryFor(challenger);
+ if (todo == null || query == null) {
+ return null;
+ }
+ return query.findByField(todo, fieldName, fieldValue);
+ }
+
+ private EntityInstance findTodoByIdentifier(
+ final ChallengerAuthData challenger, final String identifier) {
+ EntityDefinition todo = todoDefinition();
+ EntityInstanceQuery query = queryFor(challenger);
+ if (todo == null || query == null) {
+ return null;
+ }
+ return query.findByQueryIdentifier(todo, identifier);
+ }
+
+ private int countTodos(final ChallengerAuthData challenger) {
+ EntityDefinition todo = todoDefinition();
+ EntityInstanceQuery query = queryFor(challenger);
+ if (todo == null || query == null) {
+ return -1;
+ }
+ return query.count(todo);
+ }
+
+ private EntityInstanceQuery queryFor(final ChallengerAuthData challenger) {
+ return thingifier.getStore(challenger.getXChallenger()).entityQueries();
+ }
+
+ private EntityDefinition todoDefinition() {
+ return thingifier.getDefinitionNamed("todo");
+ }
+
+ String collate(Collection strings) {
StringBuilder collated = new StringBuilder();
- for(String string : strings){
+ for (String string : strings) {
collated.append(string);
collated.append(" ");
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPRequestHook.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPRequestHook.java
index f55376d7..88b71390 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPRequestHook.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPRequestHook.java
@@ -1,21 +1,21 @@
package uk.co.compendiumdev.challenge.challengehooks;
+import static uk.co.compendiumdev.thingifier.api.http.ThingifierHttpApi.HTTP_SESSION_HEADER_NAME;
+
+import java.util.List;
import uk.co.compendiumdev.challenge.CHALLENGE;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
import uk.co.compendiumdev.challenge.challengers.Challengers;
-import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
-import uk.co.compendiumdev.thingifier.api.http.HttpApiResponse;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpMethod;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpRequest;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpResponse;
import uk.co.compendiumdev.thingifier.application.sparkhttpmessageHooks.InternalHttpRequestHook;
-import java.util.List;
-
-import static uk.co.compendiumdev.thingifier.api.http.ThingifierHttpApi.HTTP_SESSION_HEADER_NAME;
-
/*
- This is an Internal HTTP Request because it covers functionality for endpoints that do not
- go through the normal API process i.e. heartbeat, challenges, challenger
- */
-public class ChallengerInternalHTTPRequestHook implements InternalHttpRequestHook {
+ This is an Internal HTTP Request because it covers functionality for endpoints that do not
+ go through the normal API process i.e. heartbeat, challenges, challenger
+*/
+public class ChallengerInternalHTTPRequestHook implements InternalHttpRequestHook {
private final Challengers challengers;
public ChallengerInternalHTTPRequestHook(final Challengers challengers) {
@@ -23,18 +23,21 @@ public ChallengerInternalHTTPRequestHook(final Challengers challengers) {
}
@Override
- public HttpApiResponse run(final HttpApiRequest request) {
+ public InternalHttpResponse run(final InternalHttpRequest request) {
// TODO: fix hooks so that they only run on a specific thingifier basis.
- // Until fixed so hooks only run on specific thingifiers, restrict this to Challenges API end points
- List validEndpointPrefixesToRunAgainst = List.of("challenger", "todo", "todos", "challenges", "heartbeat","secret");
+ // Until fixed so hooks only run on specific thingifiers, restrict this to Challenges API
+ // end points
+ List validEndpointPrefixesToRunAgainst =
+ List.of("challenger", "todo", "todos", "challenges", "heartbeat", "secret");
String[] pathSegments = request.getPath().split("/");
- if(!validEndpointPrefixesToRunAgainst.contains(pathSegments[0])){
+ if (!validEndpointPrefixesToRunAgainst.contains(pathSegments[0])) {
return null;
}
- ChallengerAuthData challenger = challengers.getChallenger(request.getHeader("X-CHALLENGER"));
- if(challenger==null){
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.getHeader("X-CHALLENGER"));
+ if (challenger == null) {
// cannot track challenges
return null;
}
@@ -45,55 +48,52 @@ public HttpApiResponse run(final HttpApiRequest request) {
// add challenger guid as session id to request
request.addHeader(HTTP_SESSION_HEADER_NAME, challenger.getXChallenger());
- HttpApiRequest.VERB method = request.getVerb();
+ InternalHttpMethod method = request.getVerb();
String path = request.getPath();
- if(method == HttpApiRequest.VERB.GET &&
- path.equals("challenges")){
+ if (method == InternalHttpMethod.GET && path.equals("challenges")) {
challengers.pass(challenger, CHALLENGE.GET_CHALLENGES);
}
- if(method == HttpApiRequest.VERB.GET &&
- path.equals("heartbeat")){
+ if (method == InternalHttpMethod.GET && path.equals("heartbeat")) {
challengers.pass(challenger, CHALLENGE.GET_HEARTBEAT_204);
}
- if(method == HttpApiRequest.VERB.DELETE &&
- path.equals("heartbeat")){
- challengers.pass(challenger,CHALLENGE.DELETE_HEARTBEAT_405);
+ if (method == InternalHttpMethod.DELETE && path.equals("heartbeat")) {
+ challengers.pass(challenger, CHALLENGE.DELETE_HEARTBEAT_405);
}
- if(method == HttpApiRequest.VERB.PATCH &&
- path.equals("heartbeat")){
- challengers.pass(challenger,CHALLENGE.PATCH_HEARTBEAT_500);
+ if (method == InternalHttpMethod.PATCH && path.equals("heartbeat")) {
+ challengers.pass(challenger, CHALLENGE.PATCH_HEARTBEAT_500);
}
- if(method == HttpApiRequest.VERB.TRACE &&
- path.equals("heartbeat")){
- challengers.pass(challenger,CHALLENGE.TRACE_HEARTBEAT_501);
+ if (method == InternalHttpMethod.TRACE && path.equals("heartbeat")) {
+ challengers.pass(challenger, CHALLENGE.TRACE_HEARTBEAT_501);
}
- if(method == HttpApiRequest.VERB.POST &&
- path.equals("heartbeat") && request.getHeader("x-http-method-override").equals("patch")){
- challengers.pass(challenger,CHALLENGE.OVERRIDE_PATCH_HEARTBEAT_500);
+ if (method == InternalHttpMethod.POST
+ && path.equals("heartbeat")
+ && request.getHeader("x-http-method-override").equalsIgnoreCase("patch")) {
+ challengers.pass(challenger, CHALLENGE.OVERRIDE_PATCH_HEARTBEAT_500);
}
- if(method == HttpApiRequest.VERB.POST &&
- path.equals("heartbeat") && request.getHeader("x-http-method-override").equals("delete")){
- challengers.pass(challenger,CHALLENGE.OVERRIDE_DELETE_HEARTBEAT_405);
+ if (method == InternalHttpMethod.POST
+ && path.equals("heartbeat")
+ && request.getHeader("x-http-method-override").equalsIgnoreCase("delete")) {
+ challengers.pass(challenger, CHALLENGE.OVERRIDE_DELETE_HEARTBEAT_405);
}
- if(method == HttpApiRequest.VERB.POST &&
- path.equals("heartbeat") && request.getHeader("x-http-method-override").equals("trace")){
- challengers.pass(challenger,CHALLENGE.OVERRIDE_TRACE_HEARTBEAT_501);
+ if (method == InternalHttpMethod.POST
+ && path.equals("heartbeat")
+ && request.getHeader("x-http-method-override").equalsIgnoreCase("trace")) {
+ challengers.pass(challenger, CHALLENGE.OVERRIDE_TRACE_HEARTBEAT_501);
}
- if(method == HttpApiRequest.VERB.GET &&
- path.equals("challenger/" + challenger.getXChallenger())){
- challengers.pass(challenger,CHALLENGE.GET_RESTORABLE_CHALLENGER_PROGRESS_STATUS);
+ if (method == InternalHttpMethod.GET
+ && path.equals("challenger/" + challenger.getXChallenger())) {
+ challengers.pass(challenger, CHALLENGE.GET_RESTORABLE_CHALLENGER_PROGRESS_STATUS);
}
return null;
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPResponseHook.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPResponseHook.java
index a363eb4d..bd3d1657 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPResponseHook.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengehooks/ChallengerInternalHTTPResponseHook.java
@@ -1,20 +1,18 @@
package uk.co.compendiumdev.challenge.challengehooks;
-import uk.co.compendiumdev.challenge.ChallengerState;
-import uk.co.compendiumdev.challenge.challengesrouting.XChallengerHeader;
-import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.BearerAuthHeaderParser;
+import static uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpMethod.*;
+
+import java.util.List;
import uk.co.compendiumdev.challenge.CHALLENGE;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
+import uk.co.compendiumdev.challenge.ChallengerState;
import uk.co.compendiumdev.challenge.challengers.Challengers;
-import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
-import uk.co.compendiumdev.thingifier.application.internalhttpconversion.InternalHttpResponse;
+import uk.co.compendiumdev.challenge.challengesrouting.XChallengerHeader;
+import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.BearerAuthHeaderParser;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpRequest;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpResponse;
import uk.co.compendiumdev.thingifier.application.sparkhttpmessageHooks.InternalHttpResponseHook;
-import java.util.List;
-
-import static uk.co.compendiumdev.thingifier.api.http.HttpApiRequest.VERB.*;
-
-
public class ChallengerInternalHTTPResponseHook implements InternalHttpResponseHook {
private final Challengers challengers;
@@ -23,85 +21,94 @@ public ChallengerInternalHTTPResponseHook(final Challengers challengers) {
this.challengers = challengers;
}
-
@Override
- public void run(final HttpApiRequest request, final InternalHttpResponse response) {
+ public void run(final InternalHttpRequest request, final InternalHttpResponse response) {
// allow cross origin requests
- // and swagger https://support.smartbear.com/swaggerhub/docs/en/edit-apis/cors-requirements-for--try-it-out-.html
+ // and swagger
+ // https://support.smartbear.com/swaggerhub/docs/en/edit-apis/cors-requirements-for--try-it-out-.html
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
// this is necessary for swagger UI to show headers in the UI
response.setHeader("Access-Control-Expose-Headers", "*");
- if (request.getVerb() == OPTIONS && request.getHeaders().headerExists("Access-Control-Allow-Methods")) {
- response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Allow-Methods"));
+ if (request.getVerb() == OPTIONS
+ && request.getHeaders().headerExists("Access-Control-Allow-Methods")) {
+ response.setHeader(
+ "Access-Control-Allow-Methods",
+ request.getHeader("Access-Control-Allow-Methods"));
}
// TODO: fix hooks so that they only run on a specific thingifier basis.
- // Until fixed so hooks only run on specific thingifiers, restrict this to Challenges API end points
- List validEndpointPrefixesToRunAgainst = List.of("challenger", "todo", "todos", "challenges", "heartbeat","secret");
+ // Until fixed so hooks only run on specific thingifiers, restrict this to Challenges API
+ // end points
+ List validEndpointPrefixesToRunAgainst =
+ List.of("challenger", "todo", "todos", "challenges", "heartbeat", "secret");
String[] pathSegments = request.getPath().split("/");
- if(!validEndpointPrefixesToRunAgainst.contains(pathSegments[0])){
+ if (!validEndpointPrefixesToRunAgainst.contains(pathSegments[0])) {
return;
}
-// boolean validEndpoint=false;
-// for(String checkPrefix : validEndpointPrefixesToRunAgainst){
-// if(request.getPath().startsWith(checkPrefix)){
-// validEndpoint=true;
-// break;
-// }
-// }
-// if(!validEndpoint){
-// return;
-// }
-
- ChallengerAuthData challenger = challengers.getChallenger(request.getHeader("X-CHALLENGER"));
+ // boolean validEndpoint=false;
+ // for(String checkPrefix : validEndpointPrefixesToRunAgainst){
+ // if(request.getPath().startsWith(checkPrefix)){
+ // validEndpoint=true;
+ // break;
+ // }
+ // }
+ // if(!validEndpoint){
+ // return;
+ // }
+
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.getHeader("X-CHALLENGER"));
// we can complete a challenge while the user is null - creating the user
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("challenger") &&
- response.getStatusCode() == 201) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("challenger")
+ && response.getStatusCode() == 201) {
// challenger did not exist so we need to find it to pass the challenge
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
- if (challenger != null && challenger.getState()== ChallengerState.NEW) {
+ if (challenger != null && challenger.getState() == ChallengerState.NEW) {
challengers.pass(challenger, CHALLENGE.CREATE_NEW_CHALLENGER);
}
- if(challenger.getXChallenger().equals(Challengers.SINGLE_PLAYER_GUID)){
+ if (challenger.getXChallenger().equals(Challengers.SINGLE_PLAYER_GUID)) {
// single player pass is simulated
challengers.pass(challenger, CHALLENGE.CREATE_NEW_CHALLENGER);
}
}
- if (request.getVerb() == GET &&
- request.getPath().startsWith("challenger/") &&
- response.getStatusCode() == 200) {
+ if (request.getVerb() == GET
+ && request.getPath().startsWith("challenger/")
+ && response.getStatusCode() == 200) {
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
- if (challenger != null && challenger.getState()== ChallengerState.LOADED_FROM_PERSISTENCE) {
+ if (challenger != null
+ && challenger.getState() == ChallengerState.LOADED_FROM_PERSISTENCE) {
challengers.pass(challenger, CHALLENGE.GET_RESTORE_EXISTING_CHALLENGER);
}
}
- if (request.getVerb() == POST &&
- request.getPath().startsWith("challenger") &&
- response.getStatusCode() == 200) {
+ if (request.getVerb() == POST
+ && request.getPath().startsWith("challenger")
+ && response.getStatusCode() == 200) {
String givenChallengerId = request.getHeader("X-Challenger");
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
- if (challenger != null && givenChallengerId.equals(challengerId) && challenger.getState()== ChallengerState.LOADED_FROM_PERSISTENCE) {
+ if (challenger != null
+ && givenChallengerId.equals(challengerId)
+ && challenger.getState() == ChallengerState.LOADED_FROM_PERSISTENCE) {
challengers.pass(challenger, CHALLENGE.POST_RESTORE_EXISTING_CHALLENGER);
}
}
- if (request.getVerb() == PUT &&
- request.getPath().startsWith("challenger/") &&
- (response.getStatusCode() == 200)) {
+ if (request.getVerb() == PUT
+ && request.getPath().startsWith("challenger/")
+ && (response.getStatusCode() == 200)) {
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
@@ -110,9 +117,9 @@ public void run(final HttpApiRequest request, final InternalHttpResponse respons
}
}
- if (request.getVerb() == PUT &&
- request.getPath().startsWith("challenger/") &&
- (response.getStatusCode() == 201)) {
+ if (request.getVerb() == PUT
+ && request.getPath().startsWith("challenger/")
+ && (response.getStatusCode() == 201)) {
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
@@ -121,9 +128,9 @@ public void run(final HttpApiRequest request, final InternalHttpResponse respons
}
}
- if (request.getVerb() == GET &&
- request.getPath().startsWith("challenger/database/") &&
- (response.getStatusCode() == 200)) {
+ if (request.getVerb() == GET
+ && request.getPath().startsWith("challenger/database/")
+ && (response.getStatusCode() == 200)) {
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
@@ -132,9 +139,9 @@ public void run(final HttpApiRequest request, final InternalHttpResponse respons
}
}
- if (request.getVerb() == PUT &&
- request.getPath().startsWith("challenger/database/") &&
- (response.getStatusCode() == 204)) {
+ if (request.getVerb() == PUT
+ && request.getPath().startsWith("challenger/database/")
+ && (response.getStatusCode() == 204)) {
String challengerId = response.getHeader("X-Challenger");
challenger = challengers.getChallenger(challengerId);
@@ -143,10 +150,11 @@ public void run(final HttpApiRequest request, final InternalHttpResponse respons
}
}
-
if (challenger == null) {
- if (!request.getPath().contentEquals("challenger") &&
- !request.getPath().contains("mirror/r") // exclude mirror endpoints from adding a challenger
+ if (!request.getPath().contentEquals("challenger")
+ && !request.getPath()
+ .contains(
+ "mirror/r") // exclude mirror endpoints from adding a challenger
) {
if (!response.getHeaders().headerExists("X-CHALLENGER")) {
@@ -154,7 +162,8 @@ public void run(final HttpApiRequest request, final InternalHttpResponse respons
}
}
// cannot track challenges
- if (response.getStatusCode() == 404 && (response.getBody() == null || response.getBody().isEmpty())) {
+ if (response.getStatusCode() == 404
+ && (response.getBody() == null || response.getBody().isEmpty())) {
setResponseAs404(request, response);
}
return;
@@ -162,149 +171,160 @@ public void run(final HttpApiRequest request, final InternalHttpResponse respons
if (challenger != null) {
if (!response.getHeaders().headerExists("X-CHALLENGER")) {
- if(!request.getPath().contains("mirror/r")){
+ if (!request.getPath().contains("mirror/r")) {
// exclude mirror endpoints from adding a challenger
XChallengerHeader.setResultHeaderBasedOnChallenger(response, challenger);
}
-
}
}
// No endpoint defined so this 404 created by Spark routing
- if (request.getVerb() == GET &&
- request.getPath().contentEquals("todo") &&
- response.getStatusCode() == 404) {
+ if (request.getVerb() == GET
+ && request.getPath().contentEquals("todo")
+ && response.getStatusCode() == 404) {
challengers.pass(challenger, CHALLENGE.GET_TODOS_NOT_PLURAL_404);
}
-
- if (request.getVerb() == OPTIONS &&
- request.getPath().contentEquals("todos") &&
- response.getStatusCode() == 204) {
+ if (request.getVerb() == OPTIONS
+ && request.getPath().contentEquals("todos")
+ && response.getStatusCode() == 204) {
// hack for backwards compatibility with initial solutions
response.setStatus(200);
challengers.pass(challenger, CHALLENGE.OPTIONS_TODOS);
}
- if(request.getVerb() == PUT && request.getPath().matches("todos/.*") && response.getStatusCode() == 400){
- if(response.getBody().contains("Cannot create todo with PUT due to Auto fields id")){
- challengers.pass(challenger,CHALLENGE.PUT_TODOS_400);
+ if (request.getVerb() == PUT
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 400) {
+ if (response.getBody().contains("Cannot create todo with PUT due to Auto fields id")) {
+ challengers.pass(challenger, CHALLENGE.PUT_TODOS_400);
}
}
- if(request.getVerb() == PUT && request.getPath().matches("todos/.*") && response.getStatusCode() == 200) {
- if (request.getBody().toLowerCase().contains("donestatus") && request.getBody().toLowerCase().contains("description")){
+ if (request.getVerb() == PUT
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 200) {
+ if (request.getBody().toLowerCase().contains("donestatus")
+ && request.getBody().toLowerCase().contains("description")) {
challengers.pass(challenger, CHALLENGE.PUT_TODOS_FULL_200);
}
}
- if(request.getVerb() == PUT && request.getPath().matches("todos/.*") && response.getStatusCode() == 400) {
- if (response.getBody().contains("title : field is mandatory")){
+ if (request.getVerb() == PUT
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 400) {
+ if (response.getBody().contains("title : field is mandatory")) {
challengers.pass(challenger, CHALLENGE.PUT_TODOS_MISSING_TITLE_400);
}
}
- if(request.getVerb() == PUT && request.getPath().matches("todos/.*") && response.getStatusCode() == 200){
- if(!request.getBody().toLowerCase().contains("donestatus") && !request.getBody().toLowerCase().contains("description")) {
+ if (request.getVerb() == PUT
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 200) {
+ if (!request.getBody().toLowerCase().contains("donestatus")
+ && !request.getBody().toLowerCase().contains("description")) {
challengers.pass(challenger, CHALLENGE.PUT_TODOS_PARTIAL_200);
}
}
- if(request.getVerb() == PUT && request.getPath().matches("todos/.*") && response.getStatusCode() == 400) {
- if (response.getBody().contains("Can not amend id from")){
+ if (request.getVerb() == PUT
+ && request.getPath().matches("todos/.*")
+ && response.getStatusCode() == 400) {
+ if (response.getBody().contains("Can not amend id from")) {
challengers.pass(challenger, CHALLENGE.PUT_TODOS_400_NO_AMEND_ID);
}
}
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("secret/token") &&
- request.getHeaders().headerExists("Authorization") &&
- request.getHeader("Authorization").length() > 10 &&
- response.getStatusCode() == 401) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("secret/token")
+ && request.getHeaders().headerExists("Authorization")
+ && request.getHeader("Authorization").length() > 10
+ && response.getStatusCode() == 401) {
challengers.pass(challenger, CHALLENGE.CREATE_SECRET_TOKEN_401);
}
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("secret/token") &&
- request.getHeaders().headerExists("Authorization") &&
- request.getHeader("Authorization").length() > 10 &&
- response.getStatusCode() == 201) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("secret/token")
+ && request.getHeaders().headerExists("Authorization")
+ && request.getHeader("Authorization").length() > 10
+ && response.getStatusCode() == 201) {
challengers.pass(challenger, CHALLENGE.CREATE_SECRET_TOKEN_201);
}
- if (request.getVerb() == GET &&
- request.getPath().contentEquals("secret/note") &&
- request.getHeaders().headerExists("X-AUTH-TOKEN") &&
- request.getHeader("X-AUTH-TOKEN").length() > 1 &&
- response.getStatusCode() == 403) {
+ if (request.getVerb() == GET
+ && request.getPath().contentEquals("secret/note")
+ && request.getHeaders().headerExists("X-AUTH-TOKEN")
+ && request.getHeader("X-AUTH-TOKEN").length() > 1
+ && response.getStatusCode() == 403) {
challengers.pass(challenger, CHALLENGE.GET_SECRET_NOTE_403);
}
- if (request.getVerb() == GET &&
- request.getPath().contentEquals("secret/note") &&
- !request.getHeaders().headerExists("X-AUTH-TOKEN") &&
- response.getStatusCode() == 401) {
+ if (request.getVerb() == GET
+ && request.getPath().contentEquals("secret/note")
+ && !request.getHeaders().headerExists("X-AUTH-TOKEN")
+ && response.getStatusCode() == 401) {
challengers.pass(challenger, CHALLENGE.GET_SECRET_NOTE_401);
}
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("secret/note") &&
- request.getHeaders().headerExists("X-AUTH-TOKEN") &&
- request.getHeader("X-AUTH-TOKEN").length() > 1 &&
- request.getBody().contains("\"note\"") &&
- response.getStatusCode() == 403) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("secret/note")
+ && request.getHeaders().headerExists("X-AUTH-TOKEN")
+ && request.getHeader("X-AUTH-TOKEN").length() > 1
+ && request.getBody().contains("\"note\"")
+ && response.getStatusCode() == 403) {
challengers.pass(challenger, CHALLENGE.POST_SECRET_NOTE_403);
}
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("secret/note") &&
- !request.getHeaders().headerExists("X-AUTH-TOKEN") &&
- request.getBody().contains("\"note\"") &&
- response.getStatusCode() == 401) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("secret/note")
+ && !request.getHeaders().headerExists("X-AUTH-TOKEN")
+ && request.getBody().contains("\"note\"")
+ && response.getStatusCode() == 401) {
challengers.pass(challenger, CHALLENGE.POST_SECRET_NOTE_401);
}
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("secret/note") &&
- request.getHeaders().headerExists("X-AUTH-TOKEN") &&
- request.getBody().contains("\"note\"") &&
- response.getStatusCode() == 200) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("secret/note")
+ && request.getHeaders().headerExists("X-AUTH-TOKEN")
+ && request.getBody().contains("\"note\"")
+ && response.getStatusCode() == 200) {
challengers.pass(challenger, CHALLENGE.POST_SECRET_NOTE_200);
}
- if (request.getVerb() == GET &&
- request.getPath().contentEquals("secret/note") &&
- request.getHeaders().headerExists("X-AUTH-TOKEN") &&
- response.getStatusCode() == 200) {
+ if (request.getVerb() == GET
+ && request.getPath().contentEquals("secret/note")
+ && request.getHeaders().headerExists("X-AUTH-TOKEN")
+ && response.getStatusCode() == 200) {
challengers.pass(challenger, CHALLENGE.GET_SECRET_NOTE_200);
}
- if (request.getVerb() == GET &&
- request.getPath().contentEquals("secret/note") &&
- request.getHeaders().headerExists("Authorization") &&
- new BearerAuthHeaderParser(request.getHeader("Authorization")).isValid() &&
- response.getStatusCode() == 200) {
+ if (request.getVerb() == GET
+ && request.getPath().contentEquals("secret/note")
+ && request.getHeaders().headerExists("Authorization")
+ && new BearerAuthHeaderParser(request.getHeader("Authorization")).isValid()
+ && response.getStatusCode() == 200) {
challengers.pass(challenger, CHALLENGE.GET_SECRET_NOTE_BEARER_200);
}
- if (request.getVerb() == POST &&
- request.getPath().contentEquals("secret/note") &&
- request.getHeaders().headerExists("Authorization") &&
- new BearerAuthHeaderParser(request.getHeader("Authorization")).isValid() &&
- request.getBody().contains("\"note\"") &&
- response.getStatusCode() == 200) {
+ if (request.getVerb() == POST
+ && request.getPath().contentEquals("secret/note")
+ && request.getHeaders().headerExists("Authorization")
+ && new BearerAuthHeaderParser(request.getHeader("Authorization")).isValid()
+ && request.getBody().contains("\"note\"")
+ && response.getStatusCode() == 200) {
challengers.pass(challenger, CHALLENGE.POST_SECRET_NOTE_BEARER_200);
}
- if (response.getStatusCode() == 404 && (response.getBody() == null || response.getBody().isEmpty())) {
+ if (response.getStatusCode() == 404
+ && (response.getBody() == null || response.getBody().isEmpty())) {
setResponseAs404(request, response);
}
}
- private void setResponseAs404(HttpApiRequest request, InternalHttpResponse response) {
+ private void setResponseAs404(InternalHttpRequest request, InternalHttpResponse response) {
if (request.getAcceptHeader() != null && !request.getAcceptHeader().isEmpty()) {
- if (request.getAcceptHeader().contains("html")){
+ if (request.getAcceptHeader().contains("html")) {
// treat as a GUI request and redirect
response.setStatus(307);
response.setHeader("Location", "/gui/404/" + request.getPath());
@@ -313,10 +333,11 @@ private void setResponseAs404(HttpApiRequest request, InternalHttpResponse respo
if (request.getAcceptHeader().startsWith("application/")) {
if (request.getAcceptHeader().endsWith("xml")) {
response.setType("application/xml");
- response.setBody("404 resource Unknown");
+ response.setBody(
+ "404 resource Unknown");
return;
}
- if(request.getAcceptHeader().endsWith("json")) {
+ if (request.getAcceptHeader().endsWith("json")) {
response.setType("application/json");
response.setBody("{\"errorMessages\":[\"404 resource Unknown\"]}");
return;
@@ -324,5 +345,4 @@ private void setResponseAs404(HttpApiRequest request, InternalHttpResponse respo
}
}
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengers/Challengers.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengers/Challengers.java
index 51c8dd01..3868fd87 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengers/Challengers.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengers/Challengers.java
@@ -1,5 +1,7 @@
package uk.co.compendiumdev.challenge.challengers;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.compendiumdev.challenge.CHALLENGE;
@@ -10,9 +12,6 @@
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
import uk.co.compendiumdev.thingifier.core.EntityRelModel;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
public class Challengers {
Logger logger = LoggerFactory.getLogger(Challengers.class);
@@ -21,19 +20,19 @@ public class Challengers {
private boolean singlePlayerMode;
Map authData;
public ChallengerAuthData SINGLE_PLAYER;
- public static final String SINGLE_PLAYER_GUID="rest-api-challenges-single-player";
+ public static final String SINGLE_PLAYER_GUID = "rest-api-challenges-single-player";
public ChallengerAuthData DEFAULT_PLAYER_DATA;
PersistenceLayer persistenceLayer;
private ThingifierApiConfig apiConfig;
private final Collection definedChallenges;
- public Challengers(EntityRelModel erModel, Collection definedChallenges){
+ public Challengers(EntityRelModel erModel, Collection definedChallenges) {
authData = new ConcurrentHashMap<>();
this.definedChallenges = definedChallenges;
SINGLE_PLAYER = new ChallengerAuthData(this.definedChallenges);
SINGLE_PLAYER.setXChallengerGUID(SINGLE_PLAYER_GUID);
DEFAULT_PLAYER_DATA = new ChallengerAuthData(this.definedChallenges);
- this.singlePlayerMode=true;
+ this.singlePlayerMode = true;
this.erModel = erModel;
}
@@ -41,54 +40,54 @@ public Collection getDefinedChallenges() {
return definedChallenges;
}
- public void setMultiPlayerMode(){
- singlePlayerMode=false;
+ public void setMultiPlayerMode() {
+ singlePlayerMode = false;
}
- public boolean isMultiPlayerMode(){
+ public boolean isMultiPlayerMode() {
return !singlePlayerMode;
}
- public boolean isSinglePlayerMode(){
+ public boolean isSinglePlayerMode() {
return singlePlayerMode;
}
- public boolean inMemory(final String challengerGuid){
- if(challengerGuid == null || challengerGuid.trim().isEmpty()) {
+ public boolean inMemory(final String challengerGuid) {
+ if (challengerGuid == null || challengerGuid.trim().isEmpty()) {
return false;
}
ChallengerAuthData challenger;
- if(singlePlayerMode){
+ if (singlePlayerMode) {
challenger = SINGLE_PLAYER;
- }else{
+ } else {
challenger = authData.get(challengerGuid);
}
return challenger != null;
}
public ChallengerAuthData getChallenger(final String challengerGuid) {
- if(singlePlayerMode){
+ if (singlePlayerMode) {
return SINGLE_PLAYER;
}
- if(challengerGuid == null || challengerGuid.trim().isEmpty()) {
+ if (challengerGuid == null || challengerGuid.trim().isEmpty()) {
return null;
}
ChallengerAuthData challenger = authData.get(challengerGuid);
-// if(challengerGuid.equals(SINGLE_PLAYER_GUID)){
-// return SINGLE_PLAYER;
-// }
+ // if(challengerGuid.equals(SINGLE_PLAYER_GUID)){
+ // return SINGLE_PLAYER;
+ // }
- if(challenger==null){
+ if (challenger == null) {
// we don't have challenger in memory, are they available in persistent store?
if (persistenceLayer != null) {
final PersistenceResponse response =
persistenceLayer.tryToLoadChallenger(this, challengerGuid.trim());
- if(response.isSuccess()){
- if(authData.containsKey(challengerGuid)) {
+ if (response.isSuccess()) {
+ if (authData.containsKey(challengerGuid)) {
challenger = authData.get(challengerGuid);
challenger.setState(ChallengerState.LOADED_FROM_PERSISTENCE);
}
@@ -100,39 +99,42 @@ public ChallengerAuthData getChallenger(final String challengerGuid) {
return challenger;
}
- public EntityRelModel getErModel(){
+ public EntityRelModel getErModel() {
return this.erModel;
}
public void purgeOldAuthData() {
- if(singlePlayerMode){
+ if (singlePlayerMode) {
return;
}
List deleteMe = new ArrayList<>();
final long cutOffTime = System.currentTimeMillis();
- for(ChallengerAuthData data : authData.values()){
- if(data.expiresAt() < cutOffTime ){
+ for (ChallengerAuthData data : authData.values()) {
+ if (data.expiresAt() < cutOffTime) {
logger.warn("PURGING AUTH: {}", data.getXChallenger());
deleteMe.add(data.getXChallenger());
- }else{
- logger.info("PURGE: {} expires in {}", data.getXChallenger(), cutOffTime - data.expiresAt());
+ } else {
+ logger.info(
+ "PURGE: {} expires in {}",
+ data.getXChallenger(),
+ cutOffTime - data.expiresAt());
}
}
- for(String deleteKey : deleteMe){
+ for (String deleteKey : deleteMe) {
delete(deleteKey);
- if(erModel!=null){
- if(erModel.getDatabaseNames().contains(deleteKey)){
+ if (erModel != null) {
+ if (erModel.getDatabaseNames().contains(deleteKey)) {
logger.warn("DELETING DATABASE: {}", deleteKey);
erModel.deleteInstanceDatabase(deleteKey);
}
}
}
- logger.info("CURRENT Challenger count: {}",authData.values().size());
- if(erModel!=null){
- logger.info("CURRENT database count: {}",erModel.getDatabaseNames().size());
+ logger.info("CURRENT Challenger count: {}", authData.values().size());
+ if (erModel != null) {
+ logger.info("CURRENT database count: {}", erModel.getDatabaseNames().size());
}
}
@@ -145,27 +147,28 @@ public ChallengerAuthData createNewChallenger() {
public void put(final ChallengerAuthData challenger) {
// todo: this should really check for single player mode and not just trust the GUID
- if(challenger.getXChallenger().contentEquals(SINGLE_PLAYER_GUID)){
+ if (challenger.getXChallenger().contentEquals(SINGLE_PLAYER_GUID)) {
SINGLE_PLAYER = challenger; // we just loaded the single player session
- }else {
+ } else {
authData.put(challenger.getXChallenger(), challenger);
}
}
- public void persistChallengerState(final ChallengerAuthData challenger){
+ public void persistChallengerState(final ChallengerAuthData challenger) {
if (persistenceLayer != null) {
- String databaseName = challenger.getXChallenger();
- persistenceLayer.saveChallengerStatus(challenger, erModel.getInstanceData(databaseName));
+ String databaseName = challenger.getXChallenger();
+ persistenceLayer.saveChallengerStatus(
+ challenger, erModel.exportInstanceDataAsJson(databaseName));
}
}
public void pass(final ChallengerAuthData challenger, final CHALLENGE challengeId) {
- if(challenger!=null) {
+ if (challenger != null) {
// todo: possibly only update challenge status if not already set
- //if(!challenger.statusOfChallenge(challengeId)) {
- challenger.pass(challengeId);
- persistChallengerState(challenger);
- //}
+ // if(!challenger.statusOfChallenge(challengeId)) {
+ challenger.pass(challengeId);
+ persistChallengerState(challenger);
+ // }
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeData.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeData.java
index 766557f8..54d8176f 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeData.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeData.java
@@ -1,8 +1,5 @@
package uk.co.compendiumdev.challenge.challenges;
-import java.util.ArrayList;
-import java.util.List;
-
public class ChallengeData {
public final String id;
@@ -10,11 +7,10 @@ public class ChallengeData {
public final String description;
public Boolean status;
- public ChallengeData(String id, String name, String description){
+ public ChallengeData(String id, String name, String description) {
this.id = id;
- this.name=name;
+ this.name = name;
this.description = description;
this.status = false;
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java
index fb8e6ca3..1bd38e40 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java
@@ -12,19 +12,20 @@ public class ChallengeDefinitionData {
public List solutions = new ArrayList<>();
public List hints = new ArrayList<>();
- public ChallengeDefinitionData(String id, String name, String description){
+ public ChallengeDefinitionData(String id, String name, String description) {
this.id = id;
- this.name=name;
+ this.name = name;
this.description = description;
this.status = false;
}
- public void addSolutionLink(final String linkText, final String linkType, final String linkData) {
+ public void addSolutionLink(
+ final String linkText, final String linkType, final String linkData) {
solutions.add(new ChallengeSolutionLink(linkText, linkType, linkData));
}
public boolean hasSolutionLinks() {
- return solutions.size()>0;
+ return solutions.size() > 0;
}
public void addSolutions(final List solutions) {
@@ -36,7 +37,7 @@ public void addHint(final String hintText, final String hintLink) {
}
public boolean hasHints() {
- return hints.size()>0;
+ return hints.size() > 0;
}
public void addHints(final List hints) {
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitions.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitions.java
index 5753f20f..81c9b013 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitions.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitions.java
@@ -1,13 +1,12 @@
package uk.co.compendiumdev.challenge.challenges;
+import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.compendiumdev.challenge.CHALLENGE;
import uk.co.compendiumdev.challenge.ChallengerConfig;
import uk.co.compendiumdev.challenge.challenges.definitions.*;
-import java.util.*;
-
public class ChallengeDefinitions {
Logger logger = LoggerFactory.getLogger(ChallengeDefinitions.class);
@@ -28,274 +27,438 @@ public Collection getChallengeSections() {
return sections;
}
-
-
- private String renderChallengeNumber(int challengeOrder){
- return String.format("%02d", challengeOrder);
- }
-
- public ChallengeDefinitions(ChallengerConfig config){
+ public ChallengeDefinitions(ChallengerConfig config) {
challengeData = new HashMap<>();
orderedChallenges = new ArrayList<>();
sections = new ArrayList<>();
int challengeOrder = 1;
- ChallengeDefinitionData aChallenge;
-
- ChallengeSection getStarted = new ChallengeSection("Getting Started",
- "If you want to track your challenge progress, in multi-user mode then you need to solve the challenges in this section to generate a unique ID that we can associate your progress with.");
+ ChallengeSection getStarted =
+ new ChallengeSection(
+ "Getting Started",
+ "If you want to track your challenge progress, in multi-user mode then you need to solve the challenges in this section to generate a unique ID that we can associate your progress with.");
sections.add(getStarted);
// create a challenger to persist challenge sessions
- storeChallengeAs(CHALLENGE.CREATE_NEW_CHALLENGER, ChallengerChallenges.createChallenger201(challengeOrder++), getStarted);
-
-
-
- ChallengeSection firstChallenge = new ChallengeSection("First Real Challenge",
- "For your first challenge, get the list of challenges. You'll be able to use this to see your progress in your API Client, as well as using the GUI.");
+ storeChallengeAs(
+ CHALLENGE.CREATE_NEW_CHALLENGER,
+ ChallengerChallenges.createChallenger201(challengeOrder++),
+ getStarted);
+
+ ChallengeSection firstChallenge =
+ new ChallengeSection(
+ "First Real Challenge",
+ "For your first challenge, get the list of challenges. You'll be able to use this to see your progress in your API Client, as well as using the GUI.");
sections.add(firstChallenge);
// GET all challenges as a list
- storeChallengeAs(CHALLENGE.GET_CHALLENGES, GetChallenges.getChallenges200(challengeOrder++), firstChallenge);
-
-
- ChallengeSection getChallenges = new ChallengeSection("GET Challenges",
- "To retrieve, or read information from an API we issue GET requests. This section has a bunch of GET request challenges to try out.");
+ storeChallengeAs(
+ CHALLENGE.GET_CHALLENGES,
+ GetChallenges.getChallenges200(challengeOrder++),
+ firstChallenge);
+
+ ChallengeSection getChallenges =
+ new ChallengeSection(
+ "GET Challenges",
+ "To retrieve, or read information from an API we issue GET requests. This section has a bunch of GET request challenges to try out.");
sections.add(getChallenges);
- storeChallengeAs(CHALLENGE.GET_TODOS, GetChallenges.getTodos200(challengeOrder++), getChallenges);
- storeChallengeAs(CHALLENGE.GET_TODOS_NOT_PLURAL_404, GetChallenges.getTodos404(challengeOrder++), getChallenges);
- storeChallengeAs(CHALLENGE.GET_TODO, GetChallenges.getTodo200(challengeOrder++), getChallenges);
- storeChallengeAs(CHALLENGE.GET_TODO_404, GetChallenges.getTodo404(challengeOrder++), getChallenges);
- storeChallengeAs(CHALLENGE.GET_TODOS_FILTERED, GetChallenges.getTodosFiltered200(challengeOrder++), getChallenges);
-
+ storeChallengeAs(
+ CHALLENGE.GET_TODOS, GetChallenges.getTodos200(challengeOrder++), getChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_TODOS_NOT_PLURAL_404,
+ GetChallenges.getTodos404(challengeOrder++),
+ getChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_TODO, GetChallenges.getTodo200(challengeOrder++), getChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_TODO_404, GetChallenges.getTodo404(challengeOrder++), getChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_TODOS_FILTERED,
+ GetChallenges.getTodosFiltered200(challengeOrder++),
+ getChallenges);
// HEAD
- ChallengeSection headChallenges = new ChallengeSection("HEAD Challenges",
- "A HEAD request, is like a GET request, but only returns the headers and status code.");
+ ChallengeSection headChallenges =
+ new ChallengeSection(
+ "HEAD Challenges",
+ "A HEAD request, is like a GET request, but only returns the headers and status code.");
sections.add(headChallenges);
- storeChallengeAs(CHALLENGE.GET_HEAD_TODOS, HeadChallenges.headTodos200(challengeOrder++), headChallenges);
-
+ storeChallengeAs(
+ CHALLENGE.GET_HEAD_TODOS,
+ HeadChallenges.headTodos200(challengeOrder++),
+ headChallenges);
// CREATE with POST
- ChallengeSection postCreateChallenges = new ChallengeSection("Creation Challenges with POST",
- "A POST request can be used to create and update data, these challenges are to 'create' data.");
+ ChallengeSection postCreateChallenges =
+ new ChallengeSection(
+ "Creation Challenges with POST",
+ "A POST request can be used to create and update data, these challenges are to 'create' data.");
sections.add(postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS, PostChallenges.postTodos201(challengeOrder++), postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_BAD_DONE_STATUS, PostChallenges.postTodosBadDoneStatus400(challengeOrder++), postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_TOO_LONG_TITLE_LENGTH, PostChallenges.postTodosTitleTooLong400(challengeOrder++), postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_TOO_LONG_DESCRIPTION_LENGTH, PostChallenges.postTodosDescriptionTooLong400(challengeOrder++), postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_MAX_OUT_TITLE_DESCRIPTION_LENGTH, PostChallenges.postTodosMaxTitleDescriptionTooLong400(challengeOrder++), postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_TOO_LONG_PAYLOAD_SIZE, PostChallenges.postTodosPayloadTooLong400(challengeOrder++), postCreateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_INVALID_EXTRA_FIELD, PostChallenges.postTodosInvalidExtraField400(challengeOrder++), postCreateChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS,
+ PostChallenges.postTodos201(challengeOrder++),
+ postCreateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_BAD_DONE_STATUS,
+ PostChallenges.postTodosBadDoneStatus400(challengeOrder++),
+ postCreateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_TOO_LONG_TITLE_LENGTH,
+ PostChallenges.postTodosTitleTooLong400(challengeOrder++),
+ postCreateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_TOO_LONG_DESCRIPTION_LENGTH,
+ PostChallenges.postTodosDescriptionTooLong400(challengeOrder++),
+ postCreateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_MAX_OUT_TITLE_DESCRIPTION_LENGTH,
+ PostChallenges.postTodosMaxTitleDescriptionTooLong400(challengeOrder++),
+ postCreateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_TOO_LONG_PAYLOAD_SIZE,
+ PostChallenges.postTodosPayloadTooLong400(challengeOrder++),
+ postCreateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_INVALID_EXTRA_FIELD,
+ PostChallenges.postTodosInvalidExtraField400(challengeOrder++),
+ postCreateChallenges);
// CREATE with PUT
- ChallengeSection putCreateChallenges = new ChallengeSection("Creation Challenges with PUT",
- "A PUT request can often used to create and update data. The todo application we are using has automatically generated ids, so you cannot use PUT to create.");
+ ChallengeSection putCreateChallenges =
+ new ChallengeSection(
+ "Creation Challenges with PUT",
+ "A PUT request can often used to create and update data. The todo application we are using has automatically generated ids, so you cannot use PUT to create.");
sections.add(putCreateChallenges);
- storeChallengeAs(CHALLENGE.PUT_TODOS_400, PutChallenges.putTodosId400(challengeOrder++), putCreateChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.PUT_TODOS_400,
+ PutChallenges.putTodosId400(challengeOrder++),
+ putCreateChallenges);
// UPDATE with POST
- ChallengeSection postUpdateChallenges = new ChallengeSection("Update Challenges with POST",
- "Use a POST request to amend something that already exists. These are 'partial' content updates so you usually don't need to have all details of the entity in the request, e.g. you could just update a title, or a description, or a status");
+ ChallengeSection postUpdateChallenges =
+ new ChallengeSection(
+ "Update Challenges with POST",
+ "Use a POST request to amend something that already exists. These are 'partial' content updates so you usually don't need to have all details of the entity in the request, e.g. you could just update a title, or a description, or a status");
sections.add(postUpdateChallenges);
- storeChallengeAs(CHALLENGE.POST_UPDATE_TODO, PostChallenges.postTodosId200(challengeOrder++), postUpdateChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_404, PostChallenges.postTodosId404(challengeOrder++), postUpdateChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.POST_UPDATE_TODO,
+ PostChallenges.postTodosId200(challengeOrder++),
+ postUpdateChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_404,
+ PostChallenges.postTodosId404(challengeOrder++),
+ postUpdateChallenges);
// UPDATE with PUT
- ChallengeSection putUpdateChallenges = new ChallengeSection("Update Challenges with PUT",
- "A PUT request can be used to amend data. REST Put requests are idempotent, they provide the same result each time.");
+ ChallengeSection putUpdateChallenges =
+ new ChallengeSection(
+ "Update Challenges with PUT",
+ "A PUT request can be used to amend data. REST Put requests are idempotent, they provide the same result each time.");
sections.add(putUpdateChallenges);
- storeChallengeAs(CHALLENGE.PUT_TODOS_FULL_200, PutChallenges.putTodosIdFull200(challengeOrder++), putUpdateChallenges);
- storeChallengeAs(CHALLENGE.PUT_TODOS_PARTIAL_200, PutChallenges.putTodosIdPartial200(challengeOrder++), putUpdateChallenges);
- storeChallengeAs(CHALLENGE.PUT_TODOS_MISSING_TITLE_400, PutChallenges.putTodosIdNoTitle400(challengeOrder++), putUpdateChallenges);
- storeChallengeAs(CHALLENGE.PUT_TODOS_400_NO_AMEND_ID, PutChallenges.putTodosIdNonMatchedIdsAmend400(challengeOrder++), putUpdateChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.PUT_TODOS_FULL_200,
+ PutChallenges.putTodosIdFull200(challengeOrder++),
+ putUpdateChallenges);
+ storeChallengeAs(
+ CHALLENGE.PUT_TODOS_PARTIAL_200,
+ PutChallenges.putTodosIdPartial200(challengeOrder++),
+ putUpdateChallenges);
+ storeChallengeAs(
+ CHALLENGE.PUT_TODOS_MISSING_TITLE_400,
+ PutChallenges.putTodosIdNoTitle400(challengeOrder++),
+ putUpdateChallenges);
+ storeChallengeAs(
+ CHALLENGE.PUT_TODOS_400_NO_AMEND_ID,
+ PutChallenges.putTodosIdNonMatchedIdsAmend400(challengeOrder++),
+ putUpdateChallenges);
// DELETE
- ChallengeSection deleteChallenges = new ChallengeSection("DELETE Challenges",
- "Use a DELETE request to delete an entity. Since this is an extreme request, normally you have to be logged in or authenticated, but we wanted to make life easier for you so we cover authentication later. Anyone can delete To Do items without authentication in this system.");
+ ChallengeSection deleteChallenges =
+ new ChallengeSection(
+ "DELETE Challenges",
+ "Use a DELETE request to delete an entity. Since this is an extreme request, normally you have to be logged in or authenticated, but we wanted to make life easier for you so we cover authentication later. Anyone can delete To Do items without authentication in this system.");
sections.add(deleteChallenges);
- storeChallengeAs(CHALLENGE.DELETE_A_TODO, DeleteChallenges.deleteTodosId200(challengeOrder++), deleteChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.DELETE_A_TODO,
+ DeleteChallenges.deleteTodosId200(challengeOrder++),
+ deleteChallenges);
// OPTIONS
- ChallengeSection optionsChallenges = new ChallengeSection("OPTIONS Challenges",
- "Use an OPTIONS verb and check the `Allow` header, this will show you what verbs are allowed to be used on an endpoint. When you test APIs it is worth checking to see if all the verbs listed are allowed or not.");
+ ChallengeSection optionsChallenges =
+ new ChallengeSection(
+ "OPTIONS Challenges",
+ "Use an OPTIONS verb and check the `Allow` header, this will show you what verbs are allowed to be used on an endpoint. When you test APIs it is worth checking to see if all the verbs listed are allowed or not.");
sections.add(optionsChallenges);
- storeChallengeAs(CHALLENGE.OPTIONS_TODOS, OptionsChallenges.optionsTodos200(challengeOrder++), optionsChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.OPTIONS_TODOS,
+ OptionsChallenges.optionsTodos200(challengeOrder++),
+ optionsChallenges);
- ChallengeSection acceptChallenges = new ChallengeSection("Accept Challenges",
- "The `Accept` header, tells the server what format you want the response to be in. By changing the `Accept` header you can specify JSON or XML.");
+ ChallengeSection acceptChallenges =
+ new ChallengeSection(
+ "Accept Challenges",
+ "The `Accept` header, tells the server what format you want the response to be in. By changing the `Accept` header you can specify JSON or XML.");
sections.add(acceptChallenges);
- storeChallengeAs(CHALLENGE.GET_ACCEPT_XML, GetChallenges.getTodosAcceptXML200(challengeOrder++), acceptChallenges);
- storeChallengeAs(CHALLENGE.GET_ACCEPT_JSON, GetChallenges.getTodosAcceptJson200(challengeOrder++), acceptChallenges);
- storeChallengeAs(CHALLENGE.GET_ACCEPT_ANY_DEFAULT_JSON, GetChallenges.getTodosAcceptAny200(challengeOrder++), acceptChallenges);
- storeChallengeAs(CHALLENGE.GET_ACCEPT_XML_PREFERRED, GetChallenges.getTodosPreferAcceptXML200(challengeOrder++), acceptChallenges);
- storeChallengeAs(CHALLENGE.GET_JSON_BY_DEFAULT_NO_ACCEPT, GetChallenges.getTodosNoAccept200(challengeOrder++), acceptChallenges);
- storeChallengeAs(CHALLENGE.GET_UNSUPPORTED_ACCEPT_406, GetChallenges.getTodosUnavailableAccept406(challengeOrder++), acceptChallenges);
-
-
-
-
- ChallengeSection contentTypeChallenges = new ChallengeSection("Content-Type Challenges",
- "The `Content-Type` header, tells the server what format type your 'body' content is, e.g. are you sending XML or JSON.");
+ storeChallengeAs(
+ CHALLENGE.GET_ACCEPT_XML,
+ GetChallenges.getTodosAcceptXML200(challengeOrder++),
+ acceptChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_ACCEPT_JSON,
+ GetChallenges.getTodosAcceptJson200(challengeOrder++),
+ acceptChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_ACCEPT_ANY_DEFAULT_JSON,
+ GetChallenges.getTodosAcceptAny200(challengeOrder++),
+ acceptChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_ACCEPT_XML_PREFERRED,
+ GetChallenges.getTodosPreferAcceptXML200(challengeOrder++),
+ acceptChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_JSON_BY_DEFAULT_NO_ACCEPT,
+ GetChallenges.getTodosNoAccept200(challengeOrder++),
+ acceptChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_UNSUPPORTED_ACCEPT_406,
+ GetChallenges.getTodosUnavailableAccept406(challengeOrder++),
+ acceptChallenges);
+
+ ChallengeSection contentTypeChallenges =
+ new ChallengeSection(
+ "Content-Type Challenges",
+ "The `Content-Type` header, tells the server what format type your 'body' content is, e.g. are you sending XML or JSON.");
sections.add(contentTypeChallenges);
- storeChallengeAs(CHALLENGE.POST_CREATE_XML, PostChallenges.postCreateTodoWithXMLAcceptXML(challengeOrder++), contentTypeChallenges);
- storeChallengeAs(CHALLENGE.POST_CREATE_JSON, PostChallenges.postCreateTodoWithJsonAcceptJson(challengeOrder++), contentTypeChallenges);
- storeChallengeAs(CHALLENGE.POST_TODOS_415, PostChallenges.postCreateUnsupportedContentType415(challengeOrder++), contentTypeChallenges);
-
-
-
+ storeChallengeAs(
+ CHALLENGE.POST_CREATE_XML,
+ PostChallenges.postCreateTodoWithXMLAcceptXML(challengeOrder++),
+ contentTypeChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_CREATE_JSON,
+ PostChallenges.postCreateTodoWithJsonAcceptJson(challengeOrder++),
+ contentTypeChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_TODOS_415,
+ PostChallenges.postCreateUnsupportedContentType415(challengeOrder++),
+ contentTypeChallenges);
// adjust the take a break challenges based on the app configuration
- ChallengeSection restoreChallenger = new ChallengeSection("Fancy a Break? Restore your session",
- "Your challenge progress can be saved, and as long as you remember you challenger ID you can restore it. Leaving a challenger idle in the system for more than 10 minutes will remove the challenger from memory. Challenger status and the todos database can be saved to, and restored from, the browser localStorage.");
+ ChallengeSection restoreChallenger =
+ new ChallengeSection(
+ "Fancy a Break? Restore your session",
+ "Your challenge progress can be saved, and as long as you remember you challenger ID you can restore it. Leaving a challenger idle in the system for more than 10 minutes will remove the challenger from memory. Challenger status and the todos database can be saved to, and restored from, the browser localStorage.");
sections.add(restoreChallenger);
// if persistence layer is set to cloud or file then the following apply
- if(config.persistenceLayer.willAutoSaveChallengerStatusToPersistenceLayer() && !config.single_player_mode) {
- storeChallengeAs(CHALLENGE.GET_RESTORE_EXISTING_CHALLENGER, ChallengerChallenges.getRestoreExistingChallenger200(challengeOrder++), restoreChallenger);
- storeChallengeAs(CHALLENGE.POST_RESTORE_EXISTING_CHALLENGER, ChallengerChallenges.postRestoreExistingChallenger200(challengeOrder++), restoreChallenger);
+ if (config.persistenceLayer.willAutoSaveChallengerStatusToPersistenceLayer()
+ && !config.single_player_mode) {
+ storeChallengeAs(
+ CHALLENGE.GET_RESTORE_EXISTING_CHALLENGER,
+ ChallengerChallenges.getRestoreExistingChallenger200(challengeOrder++),
+ restoreChallenger);
+ storeChallengeAs(
+ CHALLENGE.POST_RESTORE_EXISTING_CHALLENGER,
+ ChallengerChallenges.postRestoreExistingChallenger200(challengeOrder++),
+ restoreChallenger);
}
// GET the restorable version of challenger progress via api
- storeChallengeAs(CHALLENGE.GET_RESTORABLE_CHALLENGER_PROGRESS_STATUS, ChallengerChallenges.getRestorableExistingChallengerProgress200(challengeOrder++), restoreChallenger);
+ storeChallengeAs(
+ CHALLENGE.GET_RESTORABLE_CHALLENGER_PROGRESS_STATUS,
+ ChallengerChallenges.getRestorableExistingChallengerProgress200(challengeOrder++),
+ restoreChallenger);
// PUT to restore challenger progress via api
- storeChallengeAs(CHALLENGE.PUT_RESTORABLE_CHALLENGER_PROGRESS_STATUS, ChallengerChallenges.putRestoreChallengerProgress200(challengeOrder++), restoreChallenger);
-
- // the create with PUT is only valid in multi-user mode, pass in the mode and exclude this challenge
- if(!config.single_player_mode) {
- storeChallengeAs(CHALLENGE.PUT_NEW_RESTORED_CHALLENGER_PROGRESS_STATUS, ChallengerChallenges.putRestoreChallengerProgress201(challengeOrder++), restoreChallenger);
+ storeChallengeAs(
+ CHALLENGE.PUT_RESTORABLE_CHALLENGER_PROGRESS_STATUS,
+ ChallengerChallenges.putRestoreChallengerProgress200(challengeOrder++),
+ restoreChallenger);
+
+ // the create with PUT is only valid in multi-user mode, pass in the mode and exclude this
+ // challenge
+ if (!config.single_player_mode) {
+ storeChallengeAs(
+ CHALLENGE.PUT_NEW_RESTORED_CHALLENGER_PROGRESS_STATUS,
+ ChallengerChallenges.putRestoreChallengerProgress201(challengeOrder++),
+ restoreChallenger);
}
// GET the restorable version of todos database via api
- storeChallengeAs(CHALLENGE.GET_RESTORABLE_TODOS, ChallengerChallenges.getRestorableTodos200(challengeOrder++), restoreChallenger);
+ storeChallengeAs(
+ CHALLENGE.GET_RESTORABLE_TODOS,
+ ChallengerChallenges.getRestorableTodos200(challengeOrder++),
+ restoreChallenger);
// PUT to restore version of todos via api
- storeChallengeAs(CHALLENGE.PUT_RESTORABLE_TODOS, ChallengerChallenges.putRestorableTodos204(challengeOrder++), restoreChallenger);
-
-
-
-
+ storeChallengeAs(
+ CHALLENGE.PUT_RESTORABLE_TODOS,
+ ChallengerChallenges.putRestorableTodos204(challengeOrder++),
+ restoreChallenger);
// POST mixed content and accept
- ChallengeSection mixAcceptContentTypeChallenges = new ChallengeSection("Mix Accept and Content-Type Challenges",
- "We can mix the `Accept` and `Content-Type` headers so that we can send JSON but receive XML. These challenges encourage you to explore some combinations.");
+ ChallengeSection mixAcceptContentTypeChallenges =
+ new ChallengeSection(
+ "Mix Accept and Content-Type Challenges",
+ "We can mix the `Accept` and `Content-Type` headers so that we can send JSON but receive XML. These challenges encourage you to explore some combinations.");
sections.add(mixAcceptContentTypeChallenges);
- storeChallengeAs(CHALLENGE.POST_CREATE_XML_ACCEPT_JSON, PostChallenges.postTodosXmlToJson201(challengeOrder++), mixAcceptContentTypeChallenges);
- storeChallengeAs(CHALLENGE.POST_CREATE_JSON_ACCEPT_XML, PostChallenges.postTodosJsonToXml201(challengeOrder++), mixAcceptContentTypeChallenges);
-
-
-
-
- ChallengeSection miscStatusCodes = new ChallengeSection("Status Code Challenges",
- "Status-codes are essential to understand, so we created some challenges that help you trigger more status codes. Remember to review httpstatuses.com to learn what the status codes mean.");
+ storeChallengeAs(
+ CHALLENGE.POST_CREATE_XML_ACCEPT_JSON,
+ PostChallenges.postTodosXmlToJson201(challengeOrder++),
+ mixAcceptContentTypeChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_CREATE_JSON_ACCEPT_XML,
+ PostChallenges.postTodosJsonToXml201(challengeOrder++),
+ mixAcceptContentTypeChallenges);
+
+ ChallengeSection miscStatusCodes =
+ new ChallengeSection(
+ "Status Code Challenges",
+ "Status-codes are essential to understand, so we created some challenges that help you trigger more status codes. Remember to review httpstatuses.com to learn what the status codes mean.");
sections.add(miscStatusCodes);
- storeChallengeAs(CHALLENGE.DELETE_HEARTBEAT_405, StatusCodeChallenges.methodNotAllowed405UsingDelete(challengeOrder++), miscStatusCodes);
- storeChallengeAs(CHALLENGE.PATCH_HEARTBEAT_500, StatusCodeChallenges.serverError500UsingPatch(challengeOrder++), miscStatusCodes);
- storeChallengeAs(CHALLENGE.TRACE_HEARTBEAT_501, StatusCodeChallenges.notImplemented501UsingTrace(challengeOrder++), miscStatusCodes);
- storeChallengeAs(CHALLENGE.GET_HEARTBEAT_204, StatusCodeChallenges.noContent204UsingGet(challengeOrder++), miscStatusCodes);
-
- ChallengeSection methodOverrideChallenges = new ChallengeSection("HTTP Method Override Challenges",
- "Some HTTP Clients can not send all verbs e.g. PATCH, DELETE, PUT. Use an X-HTTP-Method-Override header to simulate these with a POST request");
+ storeChallengeAs(
+ CHALLENGE.DELETE_HEARTBEAT_405,
+ StatusCodeChallenges.methodNotAllowed405UsingDelete(challengeOrder++),
+ miscStatusCodes);
+ storeChallengeAs(
+ CHALLENGE.PATCH_HEARTBEAT_500,
+ StatusCodeChallenges.serverError500UsingPatch(challengeOrder++),
+ miscStatusCodes);
+ storeChallengeAs(
+ CHALLENGE.TRACE_HEARTBEAT_501,
+ StatusCodeChallenges.notImplemented501UsingTrace(challengeOrder++),
+ miscStatusCodes);
+ storeChallengeAs(
+ CHALLENGE.GET_HEARTBEAT_204,
+ StatusCodeChallenges.noContent204UsingGet(challengeOrder++),
+ miscStatusCodes);
+
+ ChallengeSection methodOverrideChallenges =
+ new ChallengeSection(
+ "HTTP Method Override Challenges",
+ "Some HTTP Clients can not send all verbs e.g. PATCH, DELETE, PUT. Use an X-HTTP-Method-Override header to simulate these with a POST request");
sections.add(methodOverrideChallenges);
- storeChallengeAs(CHALLENGE.OVERRIDE_DELETE_HEARTBEAT_405, StatusCodeChallenges.overridePostToDeleteFor405(challengeOrder++), methodOverrideChallenges);
- storeChallengeAs(CHALLENGE.OVERRIDE_PATCH_HEARTBEAT_500, StatusCodeChallenges.overridePostToPatchFor500(challengeOrder++), methodOverrideChallenges);
- storeChallengeAs(CHALLENGE.OVERRIDE_TRACE_HEARTBEAT_501, StatusCodeChallenges.overridePostToTraceFor501(challengeOrder++), methodOverrideChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.OVERRIDE_DELETE_HEARTBEAT_405,
+ StatusCodeChallenges.overridePostToDeleteFor405(challengeOrder++),
+ methodOverrideChallenges);
+ storeChallengeAs(
+ CHALLENGE.OVERRIDE_PATCH_HEARTBEAT_500,
+ StatusCodeChallenges.overridePostToPatchFor500(challengeOrder++),
+ methodOverrideChallenges);
+ storeChallengeAs(
+ CHALLENGE.OVERRIDE_TRACE_HEARTBEAT_501,
+ StatusCodeChallenges.overridePostToTraceFor501(challengeOrder++),
+ methodOverrideChallenges);
// authorization and authentication
- ChallengeSection authenticationChallenges = new ChallengeSection("Authentication Challenges",
- "Authentication is telling the system who you are. In multi-user mode you are already doing that with the X-CHALLENGER header, but we have added an extra level of security on the /secret section. So first Authenticate with Basic Authentication to find out the token to use for authorisation for later challenges.");
+ ChallengeSection authenticationChallenges =
+ new ChallengeSection(
+ "Authentication Challenges",
+ "Authentication is telling the system who you are. In multi-user mode you are already doing that with the X-CHALLENGER header, but we have added an extra level of security on the /secret section. So first Authenticate with Basic Authentication to find out the token to use for authorisation for later challenges.");
sections.add(authenticationChallenges);
- storeChallengeAs(CHALLENGE.CREATE_SECRET_TOKEN_401, SecretTokenChallenges.createSecretTokenNotAuthenticated401(challengeOrder++), authenticationChallenges);
- storeChallengeAs(CHALLENGE.CREATE_SECRET_TOKEN_201, SecretTokenChallenges.createSecretTokenAuthenticated201(challengeOrder++), authenticationChallenges);
-
-
-
-
- ChallengeSection authorizationChallenges = new ChallengeSection("Authorization Challenges",
- "Once the system knows who you are, authorization is if you have the correct level of access. In these challenges the authorization is granted using a custom API header X-AUTH-TOKEN or using a Bearer Authorization header.");
+ storeChallengeAs(
+ CHALLENGE.CREATE_SECRET_TOKEN_401,
+ SecretTokenChallenges.createSecretTokenNotAuthenticated401(challengeOrder++),
+ authenticationChallenges);
+ storeChallengeAs(
+ CHALLENGE.CREATE_SECRET_TOKEN_201,
+ SecretTokenChallenges.createSecretTokenAuthenticated201(challengeOrder++),
+ authenticationChallenges);
+
+ ChallengeSection authorizationChallenges =
+ new ChallengeSection(
+ "Authorization Challenges",
+ "Once the system knows who you are, authorization is if you have the correct level of access. In these challenges the authorization is granted using a custom API header X-AUTH-TOKEN or using a Bearer Authorization header.");
sections.add(authorizationChallenges);
- storeChallengeAs(CHALLENGE.GET_SECRET_NOTE_403, SecretTokenChallenges.forbiddenNotAuthorized403(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.GET_SECRET_NOTE_401, SecretTokenChallenges.invalidRequestNoAuthHeader401(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.GET_SECRET_NOTE_200, SecretTokenChallenges.authorizedGet200(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.POST_SECRET_NOTE_200, SecretTokenChallenges.authorizedUpdate200(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.POST_SECRET_NOTE_401, SecretTokenChallenges.postMissingTokenAuth401(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.POST_SECRET_NOTE_403, SecretTokenChallenges.postInvalidTokenAuth401(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.GET_SECRET_NOTE_BEARER_200, SecretTokenChallenges.getWithValidBearerToken200(challengeOrder++), authorizationChallenges);
- storeChallengeAs(CHALLENGE.POST_SECRET_NOTE_BEARER_200, SecretTokenChallenges.postUpdateWithValidBearerToken200(challengeOrder++), authorizationChallenges);
-
-
+ storeChallengeAs(
+ CHALLENGE.GET_SECRET_NOTE_403,
+ SecretTokenChallenges.forbiddenNotAuthorized403(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_SECRET_NOTE_401,
+ SecretTokenChallenges.invalidRequestNoAuthHeader401(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_SECRET_NOTE_200,
+ SecretTokenChallenges.authorizedGet200(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_SECRET_NOTE_200,
+ SecretTokenChallenges.authorizedUpdate200(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_SECRET_NOTE_401,
+ SecretTokenChallenges.postMissingTokenAuth401(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_SECRET_NOTE_403,
+ SecretTokenChallenges.postInvalidTokenAuth401(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.GET_SECRET_NOTE_BEARER_200,
+ SecretTokenChallenges.getWithValidBearerToken200(challengeOrder++),
+ authorizationChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_SECRET_NOTE_BEARER_200,
+ SecretTokenChallenges.postUpdateWithValidBearerToken200(challengeOrder++),
+ authorizationChallenges);
// misc
- ChallengeSection miscChallenges = new ChallengeSection("Miscellaneous Challenges",
- "We left these challenges to the end because they seemed fun, but... different.");
+ ChallengeSection miscChallenges =
+ new ChallengeSection(
+ "Miscellaneous Challenges",
+ "We left these challenges to the end because they seemed fun, but... different.");
sections.add(miscChallenges);
-
- storeChallengeAs(CHALLENGE.DELETE_ALL_TODOS, MiscChallenges.deleteAllTodos200(challengeOrder++), miscChallenges);
- storeChallengeAs(CHALLENGE.POST_ALL_TODOS, MiscChallenges.postAllTodos201(challengeOrder++), miscChallenges);
-
-
-
-
-
+ storeChallengeAs(
+ CHALLENGE.DELETE_ALL_TODOS,
+ MiscChallenges.deleteAllTodos200(challengeOrder++),
+ miscChallenges);
+ storeChallengeAs(
+ CHALLENGE.POST_ALL_TODOS,
+ MiscChallenges.postAllTodos201(challengeOrder),
+ miscChallenges);
Set challengeNames = new HashSet();
- for(ChallengeDefinitionData challenge : orderedChallenges){
+ for (ChallengeDefinitionData challenge : orderedChallenges) {
logger.info("Setup Challenge: " + challenge.name);
challengeNames.add(challenge.name);
}
int sectionChallengesCount = 0;
- for(ChallengeSection section : sections){
+ for (ChallengeSection section : sections) {
sectionChallengesCount += section.getChallenges().size();
}
- if(sectionChallengesCount!= orderedChallenges.size()) {
+ if (sectionChallengesCount != orderedChallenges.size()) {
throw new RuntimeException(
- "Number challenges in sections, does not match number of challenges" +
- ", possibly forgot to add section or challenge to section");
+ "Number challenges in sections, does not match number of challenges"
+ + ", possibly forgot to add section or challenge to section");
}
- if(challengeNames.size()!= orderedChallenges.size()) {
+ if (challengeNames.size() != orderedChallenges.size()) {
throw new RuntimeException(
- "Number of names, does not match number of challenges" +
- ", possible duplicate name");
+ "Number of names, does not match number of challenges"
+ + ", possible duplicate name");
}
}
private ChallengeDefinitionData storeChallengeAs(
- final CHALLENGE id,
- final ChallengeDefinitionData challenge,
- ChallengeSection section) {
+ final CHALLENGE id, final ChallengeDefinitionData challenge, ChallengeSection section) {
challengeData.put(id, challenge);
orderedChallenges.add(challenge);
@@ -304,10 +467,9 @@ private ChallengeDefinitionData storeChallengeAs(
return challenge;
}
-
public CHALLENGE getChallenge(final String name) {
- for(Map.Entrychallenge : challengeData.entrySet()){
- if(challenge.getValue().name.contentEquals(name)){
+ for (Map.Entry challenge : challengeData.entrySet()) {
+ if (challenge.getValue().name.contentEquals(name)) {
return challenge.getKey();
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeSolutionLink.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeSolutionLink.java
index 2b23b1a6..fff5f709 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeSolutionLink.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeSolutionLink.java
@@ -6,34 +6,36 @@ public class ChallengeSolutionLink {
public final String linkType;
public final String linkData;
- public ChallengeSolutionLink(final String linkText, final String linkType, final String linkUrl) {
+ public ChallengeSolutionLink(
+ final String linkText, final String linkType, final String linkUrl) {
this.linkText = linkText.trim();
- if(linkType==null){
+ if (linkType == null) {
this.linkType = "";
- }else{
+ } else {
this.linkType = linkType.trim().toUpperCase();
}
- if(linkUrl==null) {
+ if (linkUrl == null) {
this.linkData = "";
- }else{
+ } else {
this.linkData = linkUrl.trim();
}
}
public String asHtmlAHref() {
- if(linkType.equals("YOUTUBE")){
- return String.format("%s",linkData, linkText);
+ if (linkType.equals("YOUTUBE")) {
+ return String.format(
+ "%s", linkData, linkText);
}
- if(linkData.isEmpty()){
+ if (linkData.isEmpty()) {
return linkText;
}
- String target="target='_blank'";
- if(!linkData.startsWith("http")){
- target="";
+ String target = "target='_blank'";
+ if (!linkData.startsWith("http")) {
+ target = "";
}
- return String.format("%s",linkData, target, linkText);
+ return String.format("%s", linkData, target, linkText);
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengeRenderer.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengeRenderer.java
index 21594969..68b1ebc3 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengeRenderer.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengeRenderer.java
@@ -2,7 +2,7 @@
public class ChallengeRenderer {
- public static String renderChallengeNumber(int challengeOrder){
+ public static String renderChallengeNumber(int challengeOrder) {
return String.format("%02d", challengeOrder);
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengerChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengerChallenges.java
index 144520ea..e24dc0e4 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengerChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/ChallengerChallenges.java
@@ -5,132 +5,187 @@
public class ChallengerChallenges {
public static ChallengeDefinitionData createChallenger201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /challenger (201)",
- "Issue a POST request on the `/challenger` end point, with no body, to create a new challenger session. Use the generated X-CHALLENGER header in future requests to track challenge completion."
- );
- aChallenge.addHint("In multi-user mode, you need to create an X-CHALLENGER Session in order to complete any challenges or make PUT, POST, DELETE requests", "/gui/multiuser");
- aChallenge.addSolutionLink("Send request using POST to /challenger endpoint. The response has an X-CHALLENGER header, add this header X-CHALLENGER and the GUID value to all future requests.","","");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/create-session/post-challenger-201");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /challenger (201)",
+ "Issue a POST request on the `/challenger` end point, with no body, to create a new challenger session. Use the generated X-CHALLENGER header in future requests to track challenge completion.");
+ aChallenge.addHint(
+ "In multi-user mode, you need to create an X-CHALLENGER Session in order to complete any challenges or make PUT, POST, DELETE requests",
+ "/gui/multiuser");
+ aChallenge.addSolutionLink(
+ "Send request using POST to /challenger endpoint. The response has an X-CHALLENGER header, add this header X-CHALLENGER and the GUID value to all future requests.",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/create-session/post-challenger-201");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
// challenge to GET /challenger/{guid} and restore a challenger session
public static ChallengeDefinitionData getRestoreExistingChallenger200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /challenger/guid (200)",
- "Issue a GET request on the `/challenger` end point with an existing challenger GUID to restore that challenger's progress into memory."
- );
- aChallenge.addHint("In multi-user mode, you need to create an X-CHALLENGER Session first and let it go idle so it is removed in the 10 minute purge", "/gui/multiuser");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /challenger/guid (200)",
+ "Issue a GET request on the `/challenger` end point with an existing challenger GUID to restore that challenger's progress into memory.");
+ aChallenge.addHint(
+ "In multi-user mode, you need to create an X-CHALLENGER Session first and let it go idle so it is removed in the 10 minute purge",
+ "/gui/multiuser");
aChallenge.addHint("Remember to add the X-CHALLENGER header to track your progress", "");
aChallenge.addHint("Add the guid in the URL as the last part of the path", "");
- aChallenge.addSolutionLink("GET /challenger/{guid} for a challenger previously saved in the persistence store", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
+ aChallenge.addSolutionLink(
+ "GET /challenger/{guid} for a challenger previously saved in the persistence store",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
- // challenge to POST /challenger with X-CHALLENGER header of existing challenger to restore a challenger
+ // challenge to POST /challenger with X-CHALLENGER header of existing challenger to restore a
+ // challenger
public static ChallengeDefinitionData postRestoreExistingChallenger200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /challenger (existing X-CHALLENGER)",
- "Issue a POST request on the `/challenger` end point, with an existing challenger GUID as the X-CHALLENGER header to restore that challenger's progress into memory."
- );
- aChallenge.addHint("In multi-user mode, you need to create an X-CHALLENGER Session first and let it go idle so it is removed in the 10 minute purge", "/gui/multiuser");
-
- aChallenge.addSolutionLink("POST /challenger with the challenger GUID in the X-CHALLENGER header for a challenger previously saved in the persistence store", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
-
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /challenger (existing X-CHALLENGER)",
+ "Issue a POST request on the `/challenger` end point, with an existing challenger GUID as the X-CHALLENGER header to restore that challenger's progress into memory.");
+ aChallenge.addHint(
+ "In multi-user mode, you need to create an X-CHALLENGER Session first and let it go idle so it is removed in the 10 minute purge",
+ "/gui/multiuser");
+
+ aChallenge.addSolutionLink(
+ "POST /challenger with the challenger GUID in the X-CHALLENGER header for a challenger previously saved in the persistence store",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
+
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
- public static ChallengeDefinitionData getRestorableExistingChallengerProgress200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /challenger/guid (existing X-CHALLENGER)",
- "Issue a GET request on the `/challenger/{guid}` end point, with an existing challenger GUID. This will return the progress data payload that can be used to later restore your progress to this status."
- );
+ public static ChallengeDefinitionData getRestorableExistingChallengerProgress200(
+ int challengeOrder) {
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /challenger/guid (existing X-CHALLENGER)",
+ "Issue a GET request on the `/challenger/{guid}` end point, with an existing challenger GUID. This will return the progress data payload that can be used to later restore your progress to this status.");
aChallenge.addHint("A challenger must have been created already for this to work", "");
aChallenge.addHint("Remember to add the X-CHALLENGER header to track your progress", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
public static ChallengeDefinitionData putRestoreChallengerProgress200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /challenger/guid RESTORE",
- "Issue a PUT request on the `/challenger/{guid}` end point, with an existing challenger GUID to restore that challenger's progress into memory."
- );
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /challenger/guid RESTORE",
+ "Issue a PUT request on the `/challenger/{guid}` end point, with an existing challenger GUID to restore that challenger's progress into memory.");
aChallenge.addHint("Use the challenger payload returned from the earlier GET request", "");
aChallenge.addHint("Remember to add the X-CHALLENGER header to track your progress", "");
- aChallenge.addHint("The challenger should already exist in memory and this will restore status to an earlier point", "");
+ aChallenge.addHint(
+ "The challenger should already exist in memory and this will restore status to an earlier point",
+ "");
- aChallenge.addSolutionLink("Using the payload from the earlier 'GET /challenger/guid' request, use PUT to reset the challenger progress", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
+ aChallenge.addSolutionLink(
+ "Using the payload from the earlier 'GET /challenger/guid' request, use PUT to reset the challenger progress",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
public static ChallengeDefinitionData putRestoreChallengerProgress201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /challenger/guid CREATE",
- "Issue a PUT request on the `/challenger/{guid}` end point, with a challenger GUID not currently in memory to restore that challenger's progress into memory."
- );
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /challenger/guid CREATE",
+ "Issue a PUT request on the `/challenger/{guid}` end point, with a challenger GUID not currently in memory to restore that challenger's progress into memory.");
aChallenge.addHint("Use the challenger payload returned from the earlier GET request", "");
aChallenge.addHint("Remember to add the X-CHALLENGER header to track your progress", "");
- aChallenge.addHint("This will create the Challenger in memory because it should not already exist", "");
+ aChallenge.addHint(
+ "This will create the Challenger in memory because it should not already exist",
+ "");
- aChallenge.addSolutionLink("Using the payload from the earlier 'GET /challenger/guid' request, use PUT to reset the challenger progress", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
+ aChallenge.addSolutionLink(
+ "Using the payload from the earlier 'GET /challenger/guid' request, use PUT to reset the challenger progress",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
public static ChallengeDefinitionData getRestorableTodos200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /challenger/database/guid (200)",
- "Issue a GET request on the `/challenger/database/{guid}` end point, to retrieve the current todos database for the user. You can use this to restore state later."
- );
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /challenger/database/guid (200)",
+ "Issue a GET request on the `/challenger/database/{guid}` end point, to retrieve the current todos database for the user. You can use this to restore state later.");
aChallenge.addHint("Remember to add the X-CHALLENGER header to track your progress", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
public static ChallengeDefinitionData putRestorableTodos204(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /challenger/database/guid (Update)",
- "Issue a PUT request on the `/challenger/database/{guid}` end point, with a payload to restore the Todos database in memory."
- );
- aChallenge.addHint("Use the Todos database payload returned from the earlier GET request", "");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /challenger/database/guid (Update)",
+ "Issue a PUT request on the `/challenger/database/{guid}` end point, with a payload to restore the Todos database in memory.");
+ aChallenge.addHint(
+ "Use the Todos database payload returned from the earlier GET request", "");
aChallenge.addHint("Remember to add the X-CHALLENGER header to track your progress", "");
- aChallenge.addSolutionLink("Using the payload from the earlier 'GET /challenger/database/guid' request, use PUT to reset the challenger todos data", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/manage-session/save-restore-session");
+ aChallenge.addSolutionLink(
+ "Using the payload from the earlier 'GET /challenger/database/guid' request, use PUT to reset the challenger todos data",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/manage-session/save-restore-session");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tNGuZMQgHxw");
return aChallenge;
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/DeleteChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/DeleteChallenges.java
index d062d784..e0de1024 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/DeleteChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/DeleteChallenges.java
@@ -5,15 +5,18 @@
public class DeleteChallenges {
public static ChallengeDefinitionData deleteTodosId200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "DELETE /todos/{id} (200)",
- "Issue a DELETE request to successfully delete a todo");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "DELETE /todos/{id} (200)",
+ "Issue a DELETE request to successfully delete a todo");
- aChallenge.addHint("Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
+ aChallenge.addHint(
+ "Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
aChallenge.addHint("Make sure a todo with the id exists prior to issuing the request");
aChallenge.addHint("Check it was deleted by issuing a GET or HEAD on the /todos/{id}");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/delete/delete-todos-id-200");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/delete/delete-todos-id-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "6MXTkaXn9qU");
return aChallenge;
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/GetChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/GetChallenges.java
index 4c002db7..3379aca4 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/GetChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/GetChallenges.java
@@ -5,101 +5,124 @@
public class GetChallenges {
public static ChallengeDefinitionData getChallenges200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /challenges (200)",
- "Issue a GET request on the `/challenges` end point");
-
- aChallenge.addHint("Remember to add the X-CHALLENGER header so you see the progress of the challenges for your session.", "");
- aChallenge.addHint("If you issue a GET request without an X-CHALLENGER header you will see the default challenge values.", "");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /challenges (200)",
+ "Issue a GET request on the `/challenges` end point");
+
+ aChallenge.addHint(
+ "Remember to add the X-CHALLENGER header so you see the progress of the challenges for your session.",
+ "");
+ aChallenge.addHint(
+ "If you issue a GET request without an X-CHALLENGER header you will see the default challenge values.",
+ "");
aChallenge.addHint("By default the response body will be JSON format.", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/first-challenge/get-challenges-200");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/first-challenge/get-challenges-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "DrAjk2NaPRo");
return aChallenge;
}
-
public static ChallengeDefinitionData getTodos200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200)",
- "Issue a GET request on the `/todos` end point");
-
- aChallenge.addHint("Remember to add the X-CHALLENGER header so you see the data for your session.", "");
- aChallenge.addHint("If you issue a GET request without an X-CHALLENGER header you will see the default todo values.", "");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200)",
+ "Issue a GET request on the `/todos` end point");
+
+ aChallenge.addHint(
+ "Remember to add the X-CHALLENGER header so you see the data for your session.",
+ "");
+ aChallenge.addHint(
+ "If you issue a GET request without an X-CHALLENGER header you will see the default todo values.",
+ "");
aChallenge.addHint("By default the response body will be JSON format.", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-200");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "OpisB0UZq0c");
return aChallenge;
}
public static ChallengeDefinitionData getTodos404(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todo (404) not plural",
- "Issue a GET request on the `/todo` end point should 404 because nouns should be plural");
-
-
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/get/get-todo-404");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todo (404) not plural",
+ "Issue a GET request on the `/todo` end point should 404 because nouns should be plural");
+
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/get/get-todo-404");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "gAJzqgcN9dc");
return aChallenge;
}
public static ChallengeDefinitionData getTodo200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos/{id} (200)",
- "Issue a GET request on the `/todos/{id}` end point to return a specific todo");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos/{id} (200)",
+ "Issue a GET request on the `/todos/{id}` end point to return a specific todo");
- aChallenge.addHint("Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
+ aChallenge.addHint(
+ "Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-id-200");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-id-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "JDbbSY3U_rY");
return aChallenge;
}
public static ChallengeDefinitionData getTodo404(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos/{id} (404)",
- "Issue a GET request on the `/todos/{id}` end point for a todo that does not exist");
-
- aChallenge.addHint("Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos/{id} (404)",
+ "Issue a GET request on the `/todos/{id}` end point for a todo that does not exist");
+
+ aChallenge.addHint(
+ "Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
aChallenge.addHint("Make sure the id is an integer e.g. /todos/1");
aChallenge.addHint("Make sure you are using the /todos end point e.g. /todos/1");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-id-404");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-id-404");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "1S5kpd8-xfM");
return aChallenge;
}
public static ChallengeDefinitionData getTodosFiltered200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200) ?filter",
- "Issue a GET request on the `/todos` end point with a query filter to get only todos which are 'done'. There must exist both 'done' and 'not done' todos, to pass this challenge.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200) ?filter",
+ "Issue a GET request on the `/todos` end point with a query filter to get only todos which are 'done'. There must exist both 'done' and 'not done' todos, to pass this challenge.");
aChallenge.addHint("A query filter is a URL parameter using the field name and a value");
- aChallenge.addHint("A URL parameter is added to the end of a url with a ? e.g. /todos?id=1");
- aChallenge.addHint("To filter on 'done' we use the 'doneStatus' field ? e.g. ?doneStatus=true");
+ aChallenge.addHint(
+ "A URL parameter is added to the end of a url with a ? e.g. /todos?id=1");
+ aChallenge.addHint(
+ "To filter on 'done' we use the 'doneStatus' field ? e.g. ?doneStatus=true");
aChallenge.addHint("Make sure there are todos which are done, and not yet done");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-200-filter");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/get/get-todos-200-filter");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "G-sLuhyPMuw");
return aChallenge;
}
-
/*
- ACCEPT HEADERS
- */
+ ACCEPT HEADERS
+ */
// GET accept type
// specify accept type - XML
// specify accept type - JSON
@@ -108,72 +131,91 @@ public static ChallengeDefinitionData getTodosFiltered200(int challengeOrder) {
// none specified - get default
// cannot supply accepted type 406
-
public static ChallengeDefinitionData getTodosAcceptXML200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200) XML",
- "Issue a GET request on the `/todos` end point with an `Accept` header of `application/xml` to receive results in XML format");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/accept-header/get-todos-200-xml");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200) XML",
+ "Issue a GET request on the `/todos` end point with an `Accept` header of `application/xml` to receive results in XML format");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/accept-header/get-todos-200-xml");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "cLeEuZm2VG8");
return aChallenge;
}
public static ChallengeDefinitionData getTodosAcceptJson200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200) JSON",
- "Issue a GET request on the `/todos` end point with an `Accept` header of `application/json` to receive results in JSON format");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/accept-header/get-todos-200-json");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200) JSON",
+ "Issue a GET request on the `/todos` end point with an `Accept` header of `application/json` to receive results in JSON format");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/accept-header/get-todos-200-json");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "79JTHiby2Qw");
return aChallenge;
}
public static ChallengeDefinitionData getTodosAcceptAny200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200) ANY",
- "Issue a GET request on the `/todos` end point with an `Accept` header of `*/*` to receive results in default JSON format");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/accept-header/get-todos-200-any");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200) ANY",
+ "Issue a GET request on the `/todos` end point with an `Accept` header of `*/*` to receive results in default JSON format");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/accept-header/get-todos-200-any");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "O4DhJ8Ohkk8");
return aChallenge;
}
public static ChallengeDefinitionData getTodosPreferAcceptXML200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200) XML pref",
- "Issue a GET request on the `/todos` end point with an `Accept` header of `application/xml, application/json` to receive results in the preferred XML format");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/accept-header/get-todos-200-xml-pref");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200) XML pref",
+ "Issue a GET request on the `/todos` end point with an `Accept` header of `application/xml, application/json` to receive results in the preferred XML format");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/accept-header/get-todos-200-xml-pref");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "sLChuy9pc9U");
return aChallenge;
}
public static ChallengeDefinitionData getTodosNoAccept200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (200) no accept",
- "Issue a GET request on the `/todos` end point with no `Accept` header present in the message to receive results in default JSON format");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/accept-header/get-todos-200-no-accept");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (200) no accept",
+ "Issue a GET request on the `/todos` end point with no `Accept` header present in the message to receive results in default JSON format");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/accept-header/get-todos-200-no-accept");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "CSVP2PcvOdg");
return aChallenge;
}
public static ChallengeDefinitionData getTodosUnavailableAccept406(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /todos (406)",
- "Issue a GET request on the `/todos` end point with an `Accept` header `application/gzip` to receive 406 'NOT ACCEPTABLE' status code");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/accept-header/get-todos-406");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /todos (406)",
+ "Issue a GET request on the `/todos` end point with an `Accept` header `application/gzip` to receive 406 'NOT ACCEPTABLE' status code");
+
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/accept-header/get-todos-406");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "QzfbegkY1ok");
return aChallenge;
}
-
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/HeadChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/HeadChallenges.java
index 5269289a..13f645fa 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/HeadChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/HeadChallenges.java
@@ -5,13 +5,15 @@
public class HeadChallenges {
public static ChallengeDefinitionData headTodos200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "HEAD /todos (200)",
- "Issue a HEAD request on the `/todos` end point");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "HEAD /todos (200)",
+ "Issue a HEAD request on the `/todos` end point");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/head/head-todos-200");
- aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "zKbytTelP84"); return aChallenge;
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/head/head-todos-200");
+ aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "zKbytTelP84");
+ return aChallenge;
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/MiscChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/MiscChallenges.java
index 941a7025..86db3ae7 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/MiscChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/MiscChallenges.java
@@ -6,28 +6,36 @@ public class MiscChallenges {
// TODO: create solution and video for Post all
public static ChallengeDefinitionData postAllTodos201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (201) all",
- "Issue as many POST requests as it takes to add the maximum number of TODOS allowed for a user. The maximum number should be listed in the documentation."
- );
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (201) all",
+ "Issue as many POST requests as it takes to add the maximum number of TODOS allowed for a user. The maximum number should be listed in the documentation.");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/miscellaneous/create-maximum-number-todos");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/miscellaneous/create-maximum-number-todos");
return aChallenge;
}
public static ChallengeDefinitionData deleteAllTodos200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "DELETE /todos/{id} (200) all",
- "Issue a DELETE request to successfully delete the last todo in system so that there are no more todos in the system");
-
- aChallenge.addHint("After deleting the last todo, there will be no todos left in the application");
- aChallenge.addHint("Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
- aChallenge.addHint("You have to delete all the todo items in the system to complete this challenge");
-
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/miscellaneous/delete-all-todos");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "DELETE /todos/{id} (200) all",
+ "Issue a DELETE request to successfully delete the last todo in system so that there are no more todos in the system");
+
+ aChallenge.addHint(
+ "After deleting the last todo, there will be no todos left in the application");
+ aChallenge.addHint(
+ "Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
+ aChallenge.addHint(
+ "You have to delete all the todo items in the system to complete this challenge");
+
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/miscellaneous/delete-all-todos");
return aChallenge;
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/OptionsChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/OptionsChallenges.java
index 166b192a..7ec5e87f 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/OptionsChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/OptionsChallenges.java
@@ -5,12 +5,14 @@
public class OptionsChallenges {
public static ChallengeDefinitionData optionsTodos200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "OPTIONS /todos (200)",
- "Issue an OPTIONS request on the `/todos` end point. You might want to manually check the 'Allow' header in the response is as expected.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "OPTIONS /todos (200)",
+ "Issue an OPTIONS request on the `/todos` end point. You might want to manually check the 'Allow' header in the response is as expected.");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/options/options-todos-200");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/options/options-todos-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "Ld5h1TSnXWA");
return aChallenge;
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PostChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PostChallenges.java
index 371905ed..834e6638 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PostChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PostChallenges.java
@@ -4,225 +4,300 @@
public class PostChallenges {
-
-
/*
- CREATE TODOS
- */
+ CREATE TODOS
+ */
public static ChallengeDefinitionData postTodos201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (201)",
- "Issue a POST request to successfully create a todo");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (201)",
+ "Issue a POST request to successfully create a todo");
aChallenge.addHint("Add a JSON payload in the request", "");
- aChallenge.addHint("If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it", "");
+ aChallenge.addHint(
+ "If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it",
+ "");
aChallenge.addHint("You must add an X-CHALLENGER header for a valid session", "");
-
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-201");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-201");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
return aChallenge;
}
public static ChallengeDefinitionData postTodosTitleTooLong400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (400) title too long",
- "Issue a POST request to create a todo but fail length validation on the `title` field because your title exceeds maximum allowable characters.");
-
- aChallenge.addHint("The API Documentation shows the maximum allowed length of the title field", "");
-
- aChallenge.addSolutionLink("Send a POST request to /todos with a title longer than 50 characters", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-400-title-too-long");
-// aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (400) title too long",
+ "Issue a POST request to create a todo but fail length validation on the `title` field because your title exceeds maximum allowable characters.");
+
+ aChallenge.addHint(
+ "The API Documentation shows the maximum allowed length of the title field", "");
+
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos with a title longer than 50 characters", "", "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/post-create/post-todos-400-title-too-long");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
// TODO: create solution for failing title too long
return aChallenge;
}
public static ChallengeDefinitionData postTodosBadDoneStatus400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (400) doneStatus",
- "Issue a POST request to create a todo but fail validation on the `doneStatus` field");
-
- aChallenge.addHint("doneStatus should be boolean, an invalid status would be a String or a number e.g. \"invalid\"");
-
- aChallenge.addSolutionLink("Send a POST request to /todos with a non-boolean `doneStatus` e.g. {\"title\":\"a title\",\"doneStatus\":\"invalid\"}", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-400");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (400) doneStatus",
+ "Issue a POST request to create a todo but fail validation on the `doneStatus` field");
+
+ aChallenge.addHint(
+ "doneStatus should be boolean, an invalid status would be a String or a number e.g. \"invalid\"");
+
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos with a non-boolean `doneStatus` e.g. {\"title\":\"a title\",\"doneStatus\":\"invalid\"}",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-400");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
return aChallenge;
}
public static ChallengeDefinitionData postTodosDescriptionTooLong400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (400) description too long",
- "Issue a POST request to create a todo but fail length validation on the `description` because your description exceeds maximum allowable characters.");
-
- aChallenge.addHint("The API Documentation shows the maximum allowed length of the description field", "");
-
- aChallenge.addSolutionLink("Send a POST request to /todos with a description longer than 200 characters", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-400-description-too-long");
-
-// aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (400) description too long",
+ "Issue a POST request to create a todo but fail length validation on the `description` because your description exceeds maximum allowable characters.");
+
+ aChallenge.addHint(
+ "The API Documentation shows the maximum allowed length of the description field",
+ "");
+
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos with a description longer than 200 characters",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/post-create/post-todos-400-description-too-long");
+
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
// TODO: create video solution for failing description too long
return aChallenge;
}
- public static ChallengeDefinitionData postTodosMaxTitleDescriptionTooLong400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (201) max out content",
- "Issue a POST request to create a todo with maximum length title and description fields.");
+ public static ChallengeDefinitionData postTodosMaxTitleDescriptionTooLong400(
+ int challengeOrder) {
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (201) max out content",
+ "Issue a POST request to create a todo with maximum length title and description fields.");
aChallenge.addHint("Max lengths are listed in the API Documentation");
- aChallenge.addHint("CounterStrings are very useful for testing with maximum field lengths","https://eviltester.github.io/TestingApp/apps/counterstrings/counterstrings.html");
+ aChallenge.addHint(
+ "CounterStrings are very useful for testing with maximum field lengths",
+ "https://eviltester.github.io/TestingApp/apps/counterstrings/counterstrings.html");
aChallenge.addHint("Both title and description should be the correct maximum lengths");
- aChallenge.addSolutionLink("Send a POST request to /todos with a description of 200 characters and a title with 50 characters", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-201-max-content");
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos with a description of 200 characters and a title with 50 characters",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/post-create/post-todos-201-max-content");
-// aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
// TODO: create video solution for max out title and description
return aChallenge;
}
public static ChallengeDefinitionData postTodosPayloadTooLong400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (413) content too long",
- "Issue a POST request to create a todo but fail payload length validation on the `description` because your whole payload exceeds maximum allowable 5000 characters.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (413) content too long",
+ "Issue a POST request to create a todo but fail payload length validation on the `description` because your whole payload exceeds maximum allowable 5000 characters.");
aChallenge.addHint("Try using a long 5000 char string as the description or title text");
- aChallenge.addHint("CounterStrings are very useful for testing with maximum field lengths","https://eviltester.github.io/TestingApp/apps/counterstrings/counterstrings.html");
-
- aChallenge.addSolutionLink("Send a POST request to /todos with a description of 5000 characters in length", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-413-content-too-long");
-
-// aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
+ aChallenge.addHint(
+ "CounterStrings are very useful for testing with maximum field lengths",
+ "https://eviltester.github.io/TestingApp/apps/counterstrings/counterstrings.html");
+
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos with a description of 5000 characters in length",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/post-create/post-todos-413-content-too-long");
+
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
// TODO: create video solution for failing content too long
return aChallenge;
}
public static ChallengeDefinitionData postTodosInvalidExtraField400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (400) extra",
- "Issue a POST request to create a todo but fail validation because your payload contains an unrecognised field.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (400) extra",
+ "Issue a POST request to create a todo but fail validation because your payload contains an unrecognised field.");
aChallenge.addHint("Try to create a todo with a title, description and a priority");
- aChallenge.addSolutionLink("Send a POST request to /todos with a priority field e.g. {\"title\":\"a title\",\"priority\":\"extra\"}", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-create/post-todos-400-extra-field");
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos with a priority field e.g. {\"title\":\"a title\",\"priority\":\"extra\"}",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/post-create/post-todos-400-extra-field");
-// aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "tlye5bQ72g0");
// TODO: create video solution for unrecognised field names
return aChallenge;
}
/*
- UPDATE TODOs
- */
-
+ UPDATE TODOs
+ */
public static ChallengeDefinitionData postTodosId200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos/{id} (200)",
- "Issue a POST request to successfully update a todo");
-
- aChallenge.addHint("Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-update/post-todos-id-200");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos/{id} (200)",
+ "Issue a POST request to successfully update a todo");
+
+ aChallenge.addHint(
+ "Make sure you don't use {id} in the url, replace that with the id of a todo e.g. /todos/1");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/post-update/post-todos-id-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "feXdRpZ_tgs");
return aChallenge;
}
public static ChallengeDefinitionData postTodosId404(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos/{id} (404)",
- "Issue a POST request for a todo which does not exist. Expect to receive a 404 response.");
-
- aChallenge.addHint("Make sure you don't use {id} in the url, replace that with the id of a todo that does not exist e.g. /todos/100");
-
- aChallenge.addSolutionLink("Send a POST request to /todos/{id} with a valid update payload where {id} does not exist", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/post-update/post-todos-id-404");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos/{id} (404)",
+ "Issue a POST request for a todo which does not exist. Expect to receive a 404 response.");
+
+ aChallenge.addHint(
+ "Make sure you don't use {id} in the url, replace that with the id of a todo that does not exist e.g. /todos/100");
+
+ aChallenge.addSolutionLink(
+ "Send a POST request to /todos/{id} with a valid update payload where {id} does not exist",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/post-update/post-todos-id-404");
// todo add video solution and hints for POST 404
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "feXdRpZ_tgs"); return aChallenge;
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "feXdRpZ_tgs");
+ // return aChallenge;
return aChallenge;
}
/*
- CONTENT TYPE
- */
+ CONTENT TYPE
+ */
// POST control content type, and Accepting only XML ie. Accept header of `application/xml`
// control content type to create with - XML
public static ChallengeDefinitionData postCreateTodoWithXMLAcceptXML(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos XML",
- "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/xml`, and Accepting only XML ie. Accept header of `application/xml`");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/content-type-header/post-todos-xml");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos XML",
+ "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/xml`, and Accepting only XML ie. Accept header of `application/xml`");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/content-type-header/post-todos-xml");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "2-KBYHwb7MM");
return aChallenge;
}
// control content type to create with - JSON
public static ChallengeDefinitionData postCreateTodoWithJsonAcceptJson(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos JSON",
- "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/json`, and Accepting only JSON ie. Accept header of `application/json`");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/content-type-header/post-todos-json");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos JSON",
+ "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/json`, and Accepting only JSON ie. Accept header of `application/json`");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/content-type-header/post-todos-json");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "VS9qIhgp51Q");
return aChallenge;
}
// content type not supported 415 e.g. form encoded
public static ChallengeDefinitionData postCreateUnsupportedContentType415(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos (415)",
- "Issue a POST request on the `/todos` end point with an unsupported content type to generate a 415 status code");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/content-type-header/post-todos-415");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos (415)",
+ "Issue a POST request on the `/todos` end point with an unsupported content type to generate a 415 status code");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/content-type-header/post-todos-415");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "L8H-vkbXyr0");
return aChallenge;
}
-
/*
- MIXED CONTENT AND ACCEPT TYPES
- */
+ MIXED CONTENT AND ACCEPT TYPES
+ */
// content type XML - accept type JSON
public static ChallengeDefinitionData postTodosXmlToJson201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos XML to JSON",
- "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/xml` but Accept `application/json`");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/mix-accept-content/post-xml-accept-json");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos XML to JSON",
+ "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/xml` but Accept `application/json`");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/mix-accept-content/post-xml-accept-json");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "kfe7VtaV7u0");
return aChallenge;
}
// content type JSON - accept type XML
public static ChallengeDefinitionData postTodosJsonToXml201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /todos JSON to XML",
- "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/json` but Accept `application/xml`");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/mix-accept-content/post-json-accept-xml");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /todos JSON to XML",
+ "Issue a POST request on the `/todos` end point to create a todo using Content-Type `application/json` but Accept `application/xml`");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/mix-accept-content/post-json-accept-xml");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "uw1Jq8t1em4");
return aChallenge;
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PutChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PutChallenges.java
index f1a5c96c..ed38339a 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PutChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/PutChallenges.java
@@ -5,77 +5,106 @@
public class PutChallenges {
public static ChallengeDefinitionData putTodosId400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /todos/{id} (400)",
- "Issue a PUT request to unsuccessfully create a todo");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /todos/{id} (400)",
+ "Issue a PUT request to unsuccessfully create a todo");
aChallenge.addHint("Add a JSON payload in the request", "");
- aChallenge.addHint("If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it", "");
+ aChallenge.addHint(
+ "If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it",
+ "");
aChallenge.addHint("Do not include an 'id' in the payload", "");
aChallenge.addHint("The id in the URL should not exist", "");
aChallenge.addHint("You must add an X-CHALLENGER header for a valid session", "");
- aChallenge.addSolutionLink("Send a PUT request to /todos/{id} with a valid creation payload", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/put-create/put-todos-400-create");
+ aChallenge.addSolutionLink(
+ "Send a PUT request to /todos/{id} with a valid creation payload", "", "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/put-create/put-todos-400-create");
// todo: create video solution for PUT todos 400 challenge
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
return aChallenge;
}
/*
- UPDATE TODO CHALLENGES
- */
+ UPDATE TODO CHALLENGES
+ */
public static ChallengeDefinitionData putTodosIdFull200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /todos/{id} full (200)",
- "Issue a PUT request to update an existing todo with a complete payload i.e. title, description and donestatus.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /todos/{id} full (200)",
+ "Issue a PUT request to update an existing todo with a complete payload i.e. title, description and donestatus.");
aChallenge.addHint("Add a JSON payload in the request", "");
- aChallenge.addHint("If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it", "");
+ aChallenge.addHint(
+ "If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it",
+ "");
aChallenge.addHint("Do not include an 'id' in the payload", "");
-
- aChallenge.addSolutionLink("Send a PUT request to /todos/{id} with a full payload. Do not attempt to change the id.", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/put-update/put-todos-id-200-update-full");
+ aChallenge.addSolutionLink(
+ "Send a PUT request to /todos/{id} with a full payload. Do not attempt to change the id.",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/put-update/put-todos-id-200-update-full");
// todo: create solution video for PUT todos full 200 challenge
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
return aChallenge;
}
public static ChallengeDefinitionData putTodosIdPartial200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /todos/{id} partial (200)",
- "Issue a PUT request to update an existing todo with just mandatory items in payload i.e. title.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /todos/{id} partial (200)",
+ "Issue a PUT request to update an existing todo with just mandatory items in payload i.e. title.");
aChallenge.addHint("Add a JSON payload in the request", "");
- aChallenge.addHint("If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it", "");
+ aChallenge.addHint(
+ "If you don't know the format of the payload, use the response from a GET /todos/{id} request and amend it",
+ "");
aChallenge.addHint("Do not include an 'id' in the payload", "");
- aChallenge.addSolutionLink("Send a PUT request to /todos/{id} with a partial payload. Mandatory field title must be included.", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/put-update/put-todos-id-200-update-partial");
+ aChallenge.addSolutionLink(
+ "Send a PUT request to /todos/{id} with a partial payload. Mandatory field title must be included.",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/put-update/put-todos-id-200-update-partial");
// todo: create solution video for PUT todos partial 200 challenge
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
return aChallenge;
}
public static ChallengeDefinitionData putTodosIdNoTitle400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /todos/{id} no title (400)",
- "Issue a PUT request to fail to update an existing todo because title is missing in payload.");
-
-
- aChallenge.addHint("Title is required for Put requests because they are idempotent. You can amend using POST without a title, but not using a PUT.");
-
- aChallenge.addSolutionLink("Send a PUT request to /todos/{id} without a title field in the payload.", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/put-update/put-todos-id-400-no-title");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /todos/{id} no title (400)",
+ "Issue a PUT request to fail to update an existing todo because title is missing in payload.");
+
+ aChallenge.addHint(
+ "Title is required for Put requests because they are idempotent. You can amend using POST without a title, but not using a PUT.");
+
+ aChallenge.addSolutionLink(
+ "Send a PUT request to /todos/{id} without a title field in the payload.", "", "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/put-update/put-todos-id-400-no-title");
// todo: create solution video for PUT todos partial 200 challenge
@@ -83,22 +112,28 @@ public static ChallengeDefinitionData putTodosIdNoTitle400(int challengeOrder) {
}
public static ChallengeDefinitionData putTodosIdNonMatchedIdsAmend400(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PUT /todos/{id} no amend id (400)",
- "Issue a PUT request to fail to update an existing todo because id different in payload.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PUT /todos/{id} no amend id (400)",
+ "Issue a PUT request to fail to update an existing todo because id different in payload.");
// todo: create solution for PUT todos partial 200 challenge
aChallenge.addHint("ID is auto generated you can not amend it in the payload.");
- aChallenge.addHint("If you have a different id in the payload from the url then this is viewed as an amendment and you can not amend an auto generated field.");
-
-
- aChallenge.addSolutionLink("Send a PUT request to /todos/{id} with a different id in the url than in the payload.", "", "");
- aChallenge.addSolutionLink("Read Solution", "HREF", "/apichallenges/solutions/put-update/put-todos-id-400-no-amend-id");
-
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
+ aChallenge.addHint(
+ "If you have a different id in the payload from the url then this is viewed as an amendment and you can not amend an auto generated field.");
+
+ aChallenge.addSolutionLink(
+ "Send a PUT request to /todos/{id} with a different id in the url than in the payload.",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/put-update/put-todos-id-400-no-amend-id");
+
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "T0LFHwavsNA");
// TODO: add solution video for put totos id no amend id
return aChallenge;
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/SecretTokenChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/SecretTokenChallenges.java
index bde94fbf..ab01d6e0 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/SecretTokenChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/SecretTokenChallenges.java
@@ -6,133 +6,167 @@ public class SecretTokenChallenges {
// POST /secret/token with incorrect username and password credentials get 401
public static ChallengeDefinitionData createSecretTokenNotAuthenticated401(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /secret/token (401)",
- "Issue a POST request on the `/secret/token` end point and receive 401 when Basic auth username/password is not admin/password");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /secret/token (401)",
+ "Issue a POST request on the `/secret/token` end point and receive 401 when Basic auth username/password is not admin/password");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authentication/post-secret-401");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/authentication/post-secret-401");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "RSQGADU3SLA");
return aChallenge;
}
// POST /secret/token with correct username and password credentials get secret token 201
public static ChallengeDefinitionData createSecretTokenAuthenticated201(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /secret/token (201)",
- "Issue a POST request on the `/secret/token` end point and receive 201 when Basic auth username/password is admin/password");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /secret/token (201)",
+ "Issue a POST request on the `/secret/token` end point and receive 201 when Basic auth username/password is admin/password");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authentication/post-secret-201");
+ aChallenge.addSolutionLink(
+ "Read Solution", "HREF", "/apichallenges/solutions/authentication/post-secret-201");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "J2GQiuEfHkI");
return aChallenge;
}
-
// GET /secret/note with no token and 403
public static ChallengeDefinitionData forbiddenNotAuthorized403(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /secret/note (403)",
- "Issue a GET request on the `/secret/note` end point and receive 403 when X-AUTH-TOKEN does not match a valid token");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /secret/note (403)",
+ "Issue a GET request on the `/secret/note` end point and receive 403 when X-AUTH-TOKEN does not match a valid token");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/get-secret-note-403");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/get-secret-note-403");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "77mnUQezdas");
return aChallenge;
}
// GET /secret/note with invalid token and 401
public static ChallengeDefinitionData invalidRequestNoAuthHeader401(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /secret/note (401)",
- "Issue a GET request on the `/secret/note` end point and receive 401 when no X-AUTH-TOKEN header present");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /secret/note (401)",
+ "Issue a GET request on the `/secret/note` end point and receive 401 when no X-AUTH-TOKEN header present");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/get-secret-note-401");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/get-secret-note-401");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "__uZlQZ48io");
return aChallenge;
}
// GET /secret/note with token and see token
public static ChallengeDefinitionData authorizedGet200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /secret/note (200)",
- "Issue a GET request on the `/secret/note` end point receive 200 when valid X-AUTH-TOKEN used - response body should contain the note");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /secret/note (200)",
+ "Issue a GET request on the `/secret/note` end point receive 200 when valid X-AUTH-TOKEN used - response body should contain the note");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/get-secret-note-200");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/get-secret-note-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "2uRpzr2OmEY");
return aChallenge;
}
// POST /secret/note with token and update secret note
public static ChallengeDefinitionData authorizedUpdate200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /secret/note (200)",
- "Issue a POST request on the `/secret/note` end point with a note payload e.g. {\"note\":\"my note\"} and receive 200 when valid X-AUTH-TOKEN used. Note is maximum length 100 chars and will be truncated when stored.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /secret/note (200)",
+ "Issue a POST request on the `/secret/note` end point with a note payload e.g. {\"note\":\"my note\"} and receive 200 when valid X-AUTH-TOKEN used. Note is maximum length 100 chars and will be truncated when stored.");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/post-secret-note-200");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/post-secret-note-200");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "A9T9yjzEOEE");
return aChallenge;
}
// POST /secret/note with invalid token and 401
public static ChallengeDefinitionData postMissingTokenAuth401(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /secret/note (401)",
- "Issue a POST request on the `/secret/note` end point with a note payload {\"note\":\"my note\"} and receive 401 when no X-AUTH-TOKEN present");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /secret/note (401)",
+ "Issue a POST request on the `/secret/note` end point with a note payload {\"note\":\"my note\"} and receive 401 when no X-AUTH-TOKEN present");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/post-secret-note-401-403");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/post-secret-note-401-403");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "76U5TEvLzLI");
return aChallenge;
}
// POST /secret/note with no token and 403
public static ChallengeDefinitionData postInvalidTokenAuth401(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /secret/note (403)",
- "Issue a POST request on the `/secret/note` end point with a note payload {\"note\":\"my note\"} and receive 403 when X-AUTH-TOKEN does not match a valid token");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /secret/note (403)",
+ "Issue a POST request on the `/secret/note` end point with a note payload {\"note\":\"my note\"} and receive 403 when X-AUTH-TOKEN does not match a valid token");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/post-secret-note-401-403");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/post-secret-note-401-403");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "76U5TEvLzLI");
return aChallenge;
}
// GET /secret/note with bearer token authorization
public static ChallengeDefinitionData getWithValidBearerToken200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /secret/note (Bearer)",
- "Issue a GET request on the `/secret/note` end point receive 200 when using the X-AUTH-TOKEN value as an Authorization Bearer token - response body should contain the note");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /secret/note (Bearer)",
+ "Issue a GET request on the `/secret/note` end point receive 200 when using the X-AUTH-TOKEN value as an Authorization Bearer token - response body should contain the note");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/get-post-secret-note-bearer");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/get-post-secret-note-bearer");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "8GsMTZxEItw");
return aChallenge;
}
// POST /secret/note with token and update secret note
public static ChallengeDefinitionData postUpdateWithValidBearerToken200(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /secret/note (Bearer)",
- "Issue a POST request on the `/secret/note` end point with a note payload e.g. {\"note\":\"my note\"} and receive 200 when valid X-AUTH-TOKEN value used as an Authorization Bearer token. Status code 200 received. Note is maximum length 100 chars and will be truncated when stored.");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /secret/note (Bearer)",
+ "Issue a POST request on the `/secret/note` end point with a note payload e.g. {\"note\":\"my note\"} and receive 200 when valid X-AUTH-TOKEN value used as an Authorization Bearer token. Status code 200 received. Note is maximum length 100 chars and will be truncated when stored.");
aChallenge.addHint("Remember to add your X-CHALLENGER guid header");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/authorization/get-post-secret-note-bearer");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/authorization/get-post-secret-note-bearer");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "8GsMTZxEItw");
return aChallenge;
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/StatusCodeChallenges.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/StatusCodeChallenges.java
index 2b1c9f0b..97ac60a0 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/StatusCodeChallenges.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/definitions/StatusCodeChallenges.java
@@ -7,96 +7,132 @@ public class StatusCodeChallenges {
// extra status code challenges
// method not allowed - 405
public static ChallengeDefinitionData methodNotAllowed405UsingDelete(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "DELETE /heartbeat (405)",
- "Issue a DELETE request on the `/heartbeat` end point and receive 405 (Method Not Allowed)");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "DELETE /heartbeat (405)",
+ "Issue a DELETE request on the `/heartbeat` end point and receive 405 (Method Not Allowed)");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
// cannot process request server error 500
public static ChallengeDefinitionData serverError500UsingPatch(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "PATCH /heartbeat (500)",
- "Issue a PATCH request on the `/heartbeat` end point and receive 500 (internal server error)");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "PATCH /heartbeat (500)",
+ "Issue a PATCH request on the `/heartbeat` end point and receive 500 (internal server error)");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
// 501
public static ChallengeDefinitionData notImplemented501UsingTrace(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "TRACE /heartbeat (501)",
- "Issue a TRACE request on the `/heartbeat` end point and receive 501 (Not Implemented)");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "TRACE /heartbeat (501)",
+ "Issue a TRACE request on the `/heartbeat` end point and receive 501 (Not Implemented)");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
// No Content 204 - ping
public static ChallengeDefinitionData noContent204UsingGet(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "GET /heartbeat (204)",
- "Issue a GET request on the `/heartbeat` end point and receive 204 when server is running");
-
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "GET /heartbeat (204)",
+ "Issue a GET request on the `/heartbeat` end point and receive 204 when server is running");
+
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/status-codes/status-codes-405-500-501-204");
aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
-
/*
- Status codes using method overrides
- */
+ Status codes using method overrides
+ */
public static ChallengeDefinitionData overridePostToPatchFor500(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /heartbeat as PATCH (500)",
- "Issue a POST request on the `/heartbeat` end point and receive 500 when you override the Method Verb to a PATCH");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /heartbeat as PATCH (500)",
+ "Issue a POST request on the `/heartbeat` end point and receive 500 when you override the Method Verb to a PATCH");
aChallenge.addHint("Use a normal POST Request, but add an X-HTTP-Method-Override header");
- aChallenge.addSolutionLink("Add a header 'X-HTTP-Method-Override: PATCH' to a POST /heartbeat request", "","");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/method-override/all-method-overrides");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
+ aChallenge.addSolutionLink(
+ "Add a header 'X-HTTP-Method-Override: PATCH' to a POST /heartbeat request",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/method-override/all-method-overrides");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
public static ChallengeDefinitionData overridePostToDeleteFor405(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /heartbeat as DELETE (405)",
- "Issue a POST request on the `/heartbeat` end point and receive 405 when you override the Method Verb to a DELETE");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /heartbeat as DELETE (405)",
+ "Issue a POST request on the `/heartbeat` end point and receive 405 when you override the Method Verb to a DELETE");
aChallenge.addHint("Use a normal POST Request, but add an X-HTTP-Method-Override header");
- aChallenge.addSolutionLink("Add a header 'X-HTTP-Method-Override: DELETE' to a POST /heartbeat request", "","");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/method-override/all-method-overrides");
- //aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
+ aChallenge.addSolutionLink(
+ "Add a header 'X-HTTP-Method-Override: DELETE' to a POST /heartbeat request",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/method-override/all-method-overrides");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
// 501
public static ChallengeDefinitionData overridePostToTraceFor501(int challengeOrder) {
- ChallengeDefinitionData aChallenge = new ChallengeDefinitionData(
- ChallengeRenderer.renderChallengeNumber(challengeOrder),
- "POST /heartbeat as Trace (501)",
- "Issue a POST request on the `/heartbeat` end point and receive 501 (Not Implemented) when you override the Method Verb to a TRACE");
+ ChallengeDefinitionData aChallenge =
+ new ChallengeDefinitionData(
+ ChallengeRenderer.renderChallengeNumber(challengeOrder),
+ "POST /heartbeat as Trace (501)",
+ "Issue a POST request on the `/heartbeat` end point and receive 501 (Not Implemented) when you override the Method Verb to a TRACE");
aChallenge.addHint("Use a normal POST Request, but add an X-HTTP-Method-Override header");
- aChallenge.addSolutionLink("Add a header 'X-HTTP-Method-Override: TRACE' to a POST /heartbeat request", "","");
- aChallenge.addSolutionLink("Read Solution", "HREF","/apichallenges/solutions/method-override/all-method-overrides");
+ aChallenge.addSolutionLink(
+ "Add a header 'X-HTTP-Method-Override: TRACE' to a POST /heartbeat request",
+ "",
+ "");
+ aChallenge.addSolutionLink(
+ "Read Solution",
+ "HREF",
+ "/apichallenges/solutions/method-override/all-method-overrides");
-// aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
+ // aChallenge.addSolutionLink("Watch Insomnia Solution", "YOUTUBE", "SGfKVFdylVI");
return aChallenge;
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/AuthRoutes.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/AuthRoutes.java
index c252839b..263426fe 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/AuthRoutes.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/AuthRoutes.java
@@ -1,46 +1,51 @@
package uk.co.compendiumdev.challenge.challengesrouting;
+import static spark.Spark.*;
+
+import java.util.List;
import spark.Route;
-import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.AcceptHeaderParser;
-import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.BasicAuthHeaderParser;
-import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.BearerAuthHeaderParser;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
import uk.co.compendiumdev.challenge.challengers.Challengers;
import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.api.docgen.RoutingDefinition;
+import uk.co.compendiumdev.thingifier.api.docgen.RoutingStatus;
+import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
+import uk.co.compendiumdev.thingifier.api.ermodelconversion.JsonThing;
import uk.co.compendiumdev.thingifier.api.http.*;
import uk.co.compendiumdev.thingifier.api.http.bodyparser.BodyParser;
+import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.AcceptHeaderParser;
+import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.BasicAuthHeaderParser;
+import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.BearerAuthHeaderParser;
import uk.co.compendiumdev.thingifier.api.http.headers.headerparser.ContentTypeHeaderParser;
import uk.co.compendiumdev.thingifier.api.response.ApiResponse;
import uk.co.compendiumdev.thingifier.api.restapihandlers.ThingCreation;
-import uk.co.compendiumdev.thingifier.api.docgen.RoutingDefinition;
-import uk.co.compendiumdev.thingifier.api.docgen.RoutingStatus;
-import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
-import uk.co.compendiumdev.thingifier.application.internalhttpconversion.HttpApiResponseToSpark;
-import uk.co.compendiumdev.thingifier.application.internalhttpconversion.SparkToHttpApiRequest;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpRequest;
+import uk.co.compendiumdev.thingifier.application.internalhttpconversion.HttpApiResponseToInternalHttpResponse;
+import uk.co.compendiumdev.thingifier.application.internalhttpconversion.InternalHttpRequestToHttpApiRequest;
+import uk.co.compendiumdev.thingifier.application.internalhttpconversion.InternalHttpResponseToSpark;
+import uk.co.compendiumdev.thingifier.application.internalhttpconversion.SparkToInternalHttpRequest;
import uk.co.compendiumdev.thingifier.core.EntityRelModel;
import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.definitions.validation.MaximumLengthValidationRule;
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
+import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceDraft;
+import uk.co.compendiumdev.thingifier.core.domain.instances.validation.EntityInstanceStateValidator;
import uk.co.compendiumdev.thingifier.core.reporting.ValidationReport;
-import uk.co.compendiumdev.thingifier.api.ermodelconversion.JsonThing;
import uk.co.compendiumdev.thingifier.spark.SimpleSparkRouteCreator;
-import java.util.List;
-
-import static spark.Spark.*;
-
// TODO: This should be using a Thingifier to do the work of XML JSON etc... like the simulation
public class AuthRoutes {
private Thingifier secretNoteStore;
private EntityDefinition secretNote;
private ThingifierHttpApi httpApi;
private JsonThing jsonThing;
+ private final EntityInstanceStateValidator stateValidator = new EntityInstanceStateValidator();
- public void configure(final Challengers challengers,
- final ThingifierApiDocumentationDefn apiDefn) {
+ public void configure(
+ final Challengers challengers, final ThingifierApiDocumentationDefn apiDefn) {
// authentication and authorisation
// - create a 'secret' note which can be stored against session using an auth token
@@ -49,271 +54,323 @@ public void configure(final Challengers challengers,
this.secretNote = this.secretNoteStore.defineThing("secretnote", "secretnotes");
this.secretNote.addFields(
- Field.is("note", FieldType.STRING).
- makeMandatory().
- withValidation(new MaximumLengthValidationRule(100)).
- withDefaultValue("")
- );
+ Field.is("note", FieldType.STRING)
+ .makeMandatory()
+ .withValidation(new MaximumLengthValidationRule(100))
+ .withDefaultValue(""));
this.httpApi = new ThingifierHttpApi(this.secretNoteStore);
this.jsonThing = new JsonThing(this.secretNoteStore.apiConfig().jsonOutput());
+ SimpleSparkRouteCreator.addHandler(
+ "/secret/token",
+ "options",
+ (request, result) -> {
+ result.status(204);
+ // disallow POST, DELETE, PATCH, TRACE
+ result.header("Allow", "POST, OPTIONS");
+ return "";
+ });
- SimpleSparkRouteCreator.addHandler("/secret/token", "options", (request, result) ->{
- result.status(204);
- // disallow POST, DELETE, PATCH, TRACE
- result.header("Allow", "POST, OPTIONS");
- return "";
- });
-
-
- // TODO: this all feels too tightly coupled to SparkJava we should have our own routing internally that spark delegates too
+ // TODO: this all feels too tightly coupled to SparkJava we should have our own routing
+ // internally that spark delegates too
// POST /secret/token with basic auth to get a secret/token to use as X-AUTH-TOKEN header
// todo: or {username, password} payload
- post("/secret/token", (request, result) -> {
-
- BasicAuthHeaderParser basicAuth = new BasicAuthHeaderParser(request.headers("Authorization"));
-
- // admin/password as default username:password
- if(!basicAuth.matches("admin","password")){
- result.raw().setHeader("WWW-Authenticate","Basic realm=\"User Visible Realm\"");
- result.status(401);
- return "";
- }
+ post(
+ "/secret/token",
+ (request, result) -> {
+ BasicAuthHeaderParser basicAuth =
+ new BasicAuthHeaderParser(request.headers("Authorization"));
+
+ // admin/password as default username:password
+ if (!basicAuth.matches("admin", "password")) {
+ result.raw()
+ .setHeader(
+ "WWW-Authenticate", "Basic realm=\"User Visible Realm\"");
+ result.status(401);
+ return "";
+ }
- ChallengerAuthData challenger = challengers.getChallenger(request.headers("X-CHALLENGER"));
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.headers("X-CHALLENGER"));
- if(challenger==null){
- result.status(401);
- XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
- return "";
- }
+ if (challenger == null) {
+ result.status(401);
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ return "";
+ }
- // if no header X-AUTH-TOKEN then grant one
- result.raw().setHeader("X-AUTH-TOKEN", challenger.getXAuthToken());
- result.status(201);
- return "";
- });
+ // if no header X-AUTH-TOKEN then grant one
+ result.raw().setHeader("X-AUTH-TOKEN", challenger.getXAuthToken());
+ result.status(201);
+ return "";
+ });
- SimpleSparkRouteCreator.routeStatusWhenNot(405, "/secret/token", List.of("post", "options"));
+ SimpleSparkRouteCreator.routeStatusWhenNot(
+ 405, "/secret/token", List.of("post", "options"));
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.POST,
- "/secret/token",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("POST /secret/token with basic auth to get a secret/token to use as X-AUTH-TOKEN header, to allow access to the /secret/note end points.").
- addPossibleStatuses(201,401).
- secureWithBasicAuth()
- );
-
-
-
-
-
-
-
-
-
+ RoutingVerb.POST,
+ "/secret/token",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "POST /secret/token with basic auth to get a secret/token to use as X-AUTH-TOKEN header, to allow access to the /secret/note end points.")
+ .addPossibleStatuses(201, 401)
+ .secureWithBasicAuth());
// todo: GET /secret/token returns the secret token or 401 if not authenticated
-
// POST /secret/note GET /secret/note - limit note to 100 chars
// no auth token will receive a 403
// auth token which does not match the session will receive a 401
// header X-AUTH-TOKEN: token given - if token not found (then) 401
- SimpleSparkRouteCreator.addHandler("/secret/note", "options", (request, result) ->{
- result.status(204);
- // disallow POST, DELETE, PATCH, TRACE
- result.header("Allow", "GET, HEAD, POST, OPTIONS");
- return "";
- });
+ SimpleSparkRouteCreator.addHandler(
+ "/secret/note",
+ "options",
+ (request, result) -> {
+ result.status(204);
+ // disallow POST, DELETE, PATCH, TRACE
+ result.header("Allow", "GET, HEAD, POST, OPTIONS");
+ return "";
+ });
+
+ Route getSecretNote =
+ (request, result) -> {
+ String authToken = request.headers("X-AUTH-TOKEN");
+ final String authorization = request.headers("Authorization");
+
+ result.header("Content-Type", "application/json");
+
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.headers("X-CHALLENGER"));
+
+ if (challenger == null) {
+ result.status(401);
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ return "";
+ }
+ // authorization bearer token will take precedence over X-AUTH-HEADER
+ if (authorization != null && !authorization.isEmpty()) {
+ final BearerAuthHeaderParser bearerToken =
+ new BearerAuthHeaderParser(authorization);
+ if (bearerToken.isBearerToken() && bearerToken.isValid()) {
+ authToken = bearerToken.getToken();
+ }
+ }
- Route getSecretNote = (request, result) -> {
- String authToken = request.headers("X-AUTH-TOKEN");
- final String authorization = request.headers("Authorization");
+ if (authToken == null || authToken.isEmpty()) {
+ result.status(401);
+ return "";
+ }
- result.header("Content-Type", "application/json");
+ if (!authToken.contentEquals(challenger.getXAuthToken())) {
+ result.status(403); // given token is not allowed to access anything
+ return "";
+ }
- ChallengerAuthData challenger = challengers.getChallenger(request.headers("X-CHALLENGER"));
+ AcceptHeaderParser acceptHeaderParser =
+ new AcceptHeaderParser(request.headers("ACCEPT"));
+ if (!acceptHeaderParser.missingAcceptHeader()
+ && !acceptHeaderParser.isSupportedHeader()) {
+ result.status(406);
+ return "";
+ }
- if(challenger==null){
- result.status(401);
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
- return "";
- }
+ final InternalHttpRequest internalRequest =
+ SparkToInternalHttpRequest.convert(request);
+ final HttpApiRequest myRequest =
+ InternalHttpRequestToHttpApiRequest.convert(internalRequest);
- // authorization bearer token will take precedence over X-AUTH-HEADER
- if(authorization!=null && !authorization.isEmpty()){
- final BearerAuthHeaderParser bearerToken = new BearerAuthHeaderParser(authorization);
- if(bearerToken.isBearerToken() && bearerToken.isValid()){
- authToken = bearerToken.getToken();
- }
- }
+ final ApiResponse response =
+ ApiResponse.success()
+ .returnSingleDraft(
+ EntityInstanceDraft.forEntity(secretNote)
+ .withField("note", challenger.getNote()));
- if(authToken==null || authToken.isEmpty()){
- result.status(401);
- return "";
- }
+ final HttpApiResponse httpApiResponse =
+ new HttpApiResponse(
+ myRequest.getHeaders(),
+ response,
+ jsonThing,
+ this.secretNoteStore.apiConfig());
- if(!authToken.contentEquals(challenger.getXAuthToken())){
- result.status(403); // given token is not allowed to access anything
- return "";
- }
+ return InternalHttpResponseToSpark.convert(
+ HttpApiResponseToInternalHttpResponse.convert(httpApiResponse), result);
- AcceptHeaderParser acceptHeaderParser = new AcceptHeaderParser(request.headers("ACCEPT"));
- if(!acceptHeaderParser.missingAcceptHeader() && !acceptHeaderParser.isSupportedHeader()){
- result.status(406);
- return "";
- }
+ // return resultBasedOnAcceptHeader(result, request.headers("ACCEPT"),
+ // challenger.getNote());
+ };
- final HttpApiRequest myRequest = SparkToHttpApiRequest.convert(request);
+ get(
+ "/secret/note",
+ (request, result) -> {
+ return getSecretNote.handle(request, result);
+ });
- EntityInstance note = new EntityInstance(secretNote).setValue("note", challenger.getNote());
- final ApiResponse response = ApiResponse.success().returnSingleInstance(note);
+ head(
+ "/secret/note",
+ (request, result) -> {
+ getSecretNote.handle(request, result);
+ return "";
+ });
- final HttpApiResponse httpApiResponse = new HttpApiResponse(myRequest.getHeaders(), response,
- jsonThing, this.secretNoteStore.apiConfig());
+ apiDefn.addRouteToDocumentation(
+ new RoutingDefinition(
+ RoutingVerb.GET,
+ "/secret/note",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "GET /secret/note with X-AUTH-TOKEN to return the secret note for the user.")
+ .addPossibleStatuses(200, 401, 403)
+ .addCustomHeader("X-AUTH-TOKEN", "string"));
+
+ post(
+ "/secret/note",
+ (request, result) -> {
+ final String authorization = request.headers("Authorization");
+ String authToken = request.headers("X-AUTH-TOKEN");
+
+ AcceptHeaderParser acceptHeaderParser =
+ new AcceptHeaderParser(request.headers("ACCEPT"));
+ if (!acceptHeaderParser.missingAcceptHeader()
+ && !acceptHeaderParser.isSupportedHeader()) {
+ result.status(406);
+ return "";
+ }
- return HttpApiResponseToSpark.convert(httpApiResponse, result);
+ ContentTypeHeaderParser contentTypeParser =
+ new ContentTypeHeaderParser(request.headers("CONTENT-TYPE"));
+ if (!contentTypeParser.isJSON() && !contentTypeParser.isXML()) {
+ result.status(415);
+ return "";
+ }
- //return resultBasedOnAcceptHeader(result, request.headers("ACCEPT"), challenger.getNote());
- };
+ // todo: if no X-CHALLENGER provided then, search memory for authToken and use
+ // associated
+ // challenger
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.headers("X-CHALLENGER"));
- get("/secret/note", (request, result) -> {
- return getSecretNote.handle(request,result);
- });
+ if (challenger == null) {
+ result.status(401);
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ return "";
+ }
- head("/secret/note", (request, result) -> {
- getSecretNote.handle(request,result);
- return "";
- });
+ result.raw().setHeader("X-CHALLENGER", challenger.getXChallenger());
+ // set content-type header for error responses
+ if (acceptHeaderParser.hasAPreferenceForXml()) {
+ result.header("Content-Type", "application/xml");
+ } else {
+ result.header("Content-Type", "application/json");
+ }
- apiDefn.addRouteToDocumentation(
- new RoutingDefinition(
- RoutingVerb.GET,
- "/secret/note",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation(
- "GET /secret/note with X-AUTH-TOKEN to return the secret note for the user.").
- addPossibleStatuses(200,401,403).
- addCustomHeader("X-AUTH-TOKEN","string")
- );
-
-
- post("/secret/note", (request, result) -> {
-
- final String authorization = request.headers("Authorization");
- String authToken = request.headers("X-AUTH-TOKEN");
-
- AcceptHeaderParser acceptHeaderParser = new AcceptHeaderParser(request.headers("ACCEPT"));
- if(!acceptHeaderParser.missingAcceptHeader() && !acceptHeaderParser.isSupportedHeader()){
- result.status(406);
- return "";
- }
-
- ContentTypeHeaderParser contentTypeParser = new ContentTypeHeaderParser(request.headers("CONTENT-TYPE"));
- if(!contentTypeParser.isJSON() && !contentTypeParser.isXML()){
- result.status(415);
- return "";
- }
-
- // todo: if no X-CHALLENGER provided then, search memory for authToken and use associated
- // challenger
- ChallengerAuthData challenger = challengers.getChallenger(request.headers("X-CHALLENGER"));
-
- if(challenger==null){
- result.status(401);
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
- return "";
- }
-
- result.raw().setHeader("X-CHALLENGER", challenger.getXChallenger());
- // set content-type header for error responses
- if(acceptHeaderParser.hasAPreferenceForXml()){
- result.header("Content-Type", "application/xml");
- }else{
- result.header("Content-Type", "application/json");
- }
-
- // authorization bearer token will take precedence over X-AUTH-HEADER
- if(authorization!=null && !authorization.isEmpty()){
- final BearerAuthHeaderParser bearerToken = new BearerAuthHeaderParser(authorization);
- if(bearerToken.isBearerToken() && bearerToken.isValid()){
- authToken = bearerToken.getToken();
- }
- }
-
- if(authToken==null || authToken.isEmpty()){
- result.status(401);
- return "";
- }
-
- if(!authToken.contentEquals(challenger.getXAuthToken())){
- result.status(403); // given token is not allowed to access anything
- return "";
- }
-
- if(!acceptHeaderParser.missingAcceptHeader() && !acceptHeaderParser.isSupportedHeader()){
- result.status(406);
- return "";
- }
-
- final HttpApiRequest myRequest = SparkToHttpApiRequest.convert(request);
- HttpApiResponse httpApiResponse = this.httpApi.validateRequestSyntax(myRequest,
- ThingifierHttpApi.HttpVerb.POST);
-
- // TODO: this should be simpler to use by apps building on thingifier
- if(httpApiResponse==null) {
-
- ApiResponse response=null;
- response = new ThingCreation(this.secretNoteStore).with(
- new BodyParser(myRequest, List.of("secretnote")),
- this.secretNoteStore.getThingInstancesNamed("secretnote", EntityRelModel.DEFAULT_DATABASE_NAME), EntityRelModel.DEFAULT_DATABASE_NAME);
- if (!response.isErrorResponse()) {
-
- EntityInstance returnedInstance = response.getReturnedInstance();
- final List protectedFieldNames = returnedInstance.getEntity().getFieldNamesOfType(FieldType.AUTO_INCREMENT, FieldType.AUTO_GUID);
- ValidationReport validity = returnedInstance.validateFieldValues(protectedFieldNames, false);
- validity.combine(returnedInstance.validateRelationships());
-
- this.secretNoteStore.deleteThing(response.getReturnedInstance(), EntityRelModel.DEFAULT_DATABASE_NAME);
-
- if (!validity.isValid()) {
- response = ApiResponse.error(400, validity.getCombinedErrorMessages());
- }else{
- final EntityInstance postedThing = response.getReturnedInstance();
- response = ApiResponse.success().returnSingleInstance(postedThing);
- challenger.setNote(response.getReturnedInstance().
- getFieldValue("note").asString());
+ // authorization bearer token will take precedence over X-AUTH-HEADER
+ if (authorization != null && !authorization.isEmpty()) {
+ final BearerAuthHeaderParser bearerToken =
+ new BearerAuthHeaderParser(authorization);
+ if (bearerToken.isBearerToken() && bearerToken.isValid()) {
+ authToken = bearerToken.getToken();
+ }
}
- }
- httpApiResponse = new HttpApiResponse(myRequest.getHeaders(), response,
- jsonThing, this.secretNoteStore.apiConfig());
- }
+ if (authToken == null || authToken.isEmpty()) {
+ result.status(401);
+ return "";
+ }
+
+ if (!authToken.contentEquals(challenger.getXAuthToken())) {
+ result.status(403); // given token is not allowed to access anything
+ return "";
+ }
+ if (!acceptHeaderParser.missingAcceptHeader()
+ && !acceptHeaderParser.isSupportedHeader()) {
+ result.status(406);
+ return "";
+ }
- return HttpApiResponseToSpark.convert(httpApiResponse, result);
+ final InternalHttpRequest internalRequest =
+ SparkToInternalHttpRequest.convert(request);
+ final HttpApiRequest myRequest =
+ InternalHttpRequestToHttpApiRequest.convert(internalRequest);
+ HttpApiResponse httpApiResponse =
+ this.httpApi.validateRequestSyntax(
+ myRequest, ThingifierHttpApi.HttpVerb.POST);
+
+ // TODO: this should be simpler to use by apps building on thingifier
+ if (httpApiResponse == null) {
+
+ ApiResponse response =
+ new ThingCreation(this.secretNoteStore)
+ .with(
+ new BodyParser(myRequest, List.of("secretnote")),
+ this.secretNote,
+ EntityRelModel.DEFAULT_DATABASE_NAME);
+ if (!response.isErrorResponse()) {
+
+ EntityInstance returnedInstance = response.getReturnedInstance();
+ final List protectedFieldNames =
+ returnedInstance
+ .getEntity()
+ .getFieldNamesOfType(
+ FieldType.AUTO_INCREMENT, FieldType.AUTO_GUID);
+ ValidationReport validity =
+ stateValidator.validateFields(
+ returnedInstance, protectedFieldNames, false);
+ validity.combine(
+ secretNoteStore
+ .getStore(EntityRelModel.DEFAULT_DATABASE_NAME)
+ .relationships()
+ .validate(returnedInstance));
+
+ this.secretNoteStore.deleteThing(
+ response.getReturnedInstance(),
+ EntityRelModel.DEFAULT_DATABASE_NAME);
+
+ if (!validity.isValid()) {
+ response =
+ ApiResponse.error(400, validity.getCombinedErrorMessages());
+ } else {
+ final EntityInstance postedThing = response.getReturnedInstance();
+ response = ApiResponse.success().returnSingleInstance(postedThing);
+ challenger.setNote(
+ response.getReturnedInstance()
+ .getFieldValue("note")
+ .asString());
+ }
+ }
+
+ httpApiResponse =
+ new HttpApiResponse(
+ myRequest.getHeaders(),
+ response,
+ jsonThing,
+ this.secretNoteStore.apiConfig());
+ }
- });
+ return InternalHttpResponseToSpark.convert(
+ HttpApiResponseToInternalHttpResponse.convert(httpApiResponse), result);
+ });
- SimpleSparkRouteCreator.routeStatusWhenNot(405, "/secret/note", List.of("get", "post", "head", "options"));
+ SimpleSparkRouteCreator.routeStatusWhenNot(
+ 405, "/secret/note", List.of("get", "post", "head", "options"));
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.POST,
- "/secret/note",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("POST /secret/note with X-AUTH-TOKEN, and a payload of `{'note':'contents of note'}` to amend the contents of the secret note.").
- addPossibleStatuses(200,400,401,403).
- addCustomHeader("X-AUTH-TOKEN","string")
- );
+ RoutingVerb.POST,
+ "/secret/note",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "POST /secret/note with X-AUTH-TOKEN, and a payload of `{'note':'contents of note'}` to amend the contents of the secret note.")
+ .addPossibleStatuses(200, 400, 401, 403)
+ .addCustomHeader("X-AUTH-TOKEN", "string"));
}
-
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerIpAddressTracker.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerIpAddressTracker.java
index 029cdf15..8884d5bc 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerIpAddressTracker.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerIpAddressTracker.java
@@ -17,12 +17,12 @@ public class ChallengerIpAddressTracker {
public ChallengerIpAddressTracker(int maxChallengersPerIp, boolean addressLimitingOn) {
maxChallengers = maxChallengersPerIp;
ipaddresses = new ConcurrentHashMap<>();
- this.addressLimitingOn=addressLimitingOn;
+ this.addressLimitingOn = addressLimitingOn;
}
public void purgeEmptyIpAddresses(Set existingChallengerGuids) {
- for(String anIp : ipaddresses.keySet()) {
+ for (String anIp : ipaddresses.keySet()) {
ConcurrentSkipListSet challengerGuids = ipaddresses.get(anIp);
List challengersToRemove = new ArrayList<>();
@@ -37,8 +37,8 @@ public void purgeEmptyIpAddresses(Set existingChallengerGuids) {
}
// purge any empty ip addresses
- for(String anIp : ipaddresses.keySet()){
- if(ipaddresses.get(anIp).isEmpty()){
+ for (String anIp : ipaddresses.keySet()) {
+ if (ipaddresses.get(anIp).isEmpty()) {
ipaddresses.remove(anIp);
}
}
@@ -46,9 +46,9 @@ public void purgeEmptyIpAddresses(Set existingChallengerGuids) {
public boolean hasLimitBeenReachedFor(String ip) {
- if(!addressLimitingOn) return false;
+ if (!addressLimitingOn) return false;
- if(countFor(ip) guids = new ConcurrentSkipListSet<>();
guids.add(xChallengerGuid);
ipaddresses.put(ip, guids);
@@ -80,7 +80,7 @@ public boolean isTrackingIp(String ip) {
}
public int countFor(String ip) {
- if(isTrackingIp(ip)){
+ if (isTrackingIp(ip)) {
return ipaddresses.get(ip).size();
}
return 0;
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerTrackingRoutes.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerTrackingRoutes.java
index c84eb160..da7c73d3 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerTrackingRoutes.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengerTrackingRoutes.java
@@ -1,6 +1,9 @@
package uk.co.compendiumdev.challenge.challengesrouting;
+import static spark.Spark.*;
+
import com.google.gson.Gson;
+import java.util.*;
import spark.Route;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
import uk.co.compendiumdev.challenge.challengers.Challengers;
@@ -12,81 +15,83 @@
import uk.co.compendiumdev.thingifier.api.response.ApiResponseError;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
-import uk.co.compendiumdev.thingifier.core.domain.instances.ERInstanceData;
import uk.co.compendiumdev.thingifier.spark.SimpleSparkRouteCreator;
-import java.util.*;
-
-import static spark.Spark.*;
-
public class ChallengerTrackingRoutes {
- final int MAX_CHALLENGERS_PER_IP=100;
-
-
- public void configure(final Challengers challengers,
- final boolean single_player_mode,
- final ThingifierApiDocumentationDefn apiDefn,
- final PersistenceLayer persistenceLayer,
- final Thingifier thingifier,
- ChallengeDefinitions challengeDefinitions){
+ final int MAX_CHALLENGERS_PER_IP = 100;
+ public void configure(
+ final Challengers challengers,
+ final boolean single_player_mode,
+ final ThingifierApiDocumentationDefn apiDefn,
+ final PersistenceLayer persistenceLayer,
+ final Thingifier thingifier,
+ ChallengeDefinitions challengeDefinitions) {
- // todo: control max challengers and ip address limiting dynamically through admin interface and via environment variables
+ // todo: control max challengers and ip address limiting dynamically through admin interface
+ // and via environment variables
// until then, leave this off by passing in false
- ChallengerIpAddressTracker ipAddressTracker = new ChallengerIpAddressTracker(MAX_CHALLENGERS_PER_IP, false);
-
-
-
- Route getChallengerId = (request, result) -> {
-
- ChallengerAuthData challenger=null;
- String xChallengerGuid = request.params("id");
-
- if(xChallengerGuid != null && !xChallengerGuid.trim().isEmpty()){
- challenger = challengers.getChallenger(xChallengerGuid);
- if(challenger!=null){
- challenger.touch();
- thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(challenger.getXChallenger());
- result.status(200);
- result.header("content-type", "application/json");
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
- return challenger.asJson();
- }else{
- result.status(404);
- }
- }else{
- result.status(404);
- }
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
- return "";
- };
-
- SimpleSparkRouteCreator.addHandler("/challenger/:id", "options", (request, result) ->{
- result.status(204);
- // disallow POST, DELETE, PATCH, TRACE
- result.header("Allow", "GET, PUT, HEAD, OPTIONS");
- return "";
- });
-
- SimpleSparkRouteCreator.routeStatusWhenNot(405, "/challenger/:id", List.of("get", "put", "head", "options"));
+ ChallengerIpAddressTracker ipAddressTracker =
+ new ChallengerIpAddressTracker(MAX_CHALLENGERS_PER_IP, false);
+
+ Route getChallengerId =
+ (request, result) -> {
+ ChallengerAuthData challenger = null;
+ String xChallengerGuid = request.params("id");
+
+ if (xChallengerGuid != null && !xChallengerGuid.trim().isEmpty()) {
+ challenger = challengers.getChallenger(xChallengerGuid);
+ if (challenger != null) {
+ challenger.touch();
+ thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(
+ challenger.getXChallenger());
+ result.status(200);
+ result.header("content-type", "application/json");
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ return challenger.asJson();
+ } else {
+ result.status(404);
+ }
+ } else {
+ result.status(404);
+ }
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ return "";
+ };
+
+ SimpleSparkRouteCreator.addHandler(
+ "/challenger/:id",
+ "options",
+ (request, result) -> {
+ result.status(204);
+ // disallow POST, DELETE, PATCH, TRACE
+ result.header("Allow", "GET, PUT, HEAD, OPTIONS");
+ return "";
+ });
+
+ SimpleSparkRouteCreator.routeStatusWhenNot(
+ 405, "/challenger/:id", List.of("get", "put", "head", "options"));
// refresh challenger to avoid purging
- get("/challenger/:id", (request, result) -> {
- return getChallengerId.handle(request, result);
- });
-
- head("/challenger/:id", (request, result) -> {
- getChallengerId.handle(request, result);
- return "";
- });
-
- if(!single_player_mode) {
+ get(
+ "/challenger/:id",
+ (request, result) -> {
+ return getChallengerId.handle(request, result);
+ });
+
+ head(
+ "/challenger/:id",
+ (request, result) -> {
+ getChallengerId.handle(request, result);
+ return "";
+ });
+
+ if (!single_player_mode) {
// make sure the X-CHALLENGER header shows up in the swagger when in multi-user mode
apiDefn.addCustomHeaderWhenRouteNotMatches(
- new RoutingDefinition(RoutingVerb.POST,"/challenger", null, null),
- new HeaderDefinition("X-CHALLENGER", "guid")
- );
+ new RoutingDefinition(RoutingVerb.POST, "/challenger", null, null),
+ new HeaderDefinition("X-CHALLENGER", "guid"));
}
Field guidField = new Field("guid", FieldType.AUTO_GUID);
@@ -94,300 +99,342 @@ public void configure(final Challengers challengers,
// Document the endpoint
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.GET,
- "/challenger/:guid",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("Get a challenger in Json format to allow continued tracking of challenges.").
- addPossibleStatuses(200,404).
- addRequestUrlParam(guidField));
+ RoutingVerb.GET,
+ "/challenger/:guid",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "Get a challenger in Json format to allow continued tracking of challenges.")
+ .addPossibleStatuses(200, 404)
+ .addRequestUrlParam(guidField));
// endpoint to restore a saved challenger status from UI
- put("/challenger/:id", (request, result) -> {
- ChallengerAuthData challenger;
-
- // for which challenger?
- String xChallengerGuid = request.params("id");
-
- if(xChallengerGuid==null){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID");
- }
-
- if(!single_player_mode) {
- try {
- UUID.fromString(xChallengerGuid);
- } catch (Exception e) {
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID " + e.getMessage());
- }
- }
-
- // try and parse payload, fail if nonsense
- try {
- challenger = new Gson().fromJson(request.body(), ChallengerAuthData.class);
- }catch (Exception e){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson(e.getMessage());
- }
-
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
-
- if(challenger==null){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Payload");
- }
-
- // check payload against id
- if(!challenger.getXChallenger().equals(xChallengerGuid)){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("URL GUID does not match payload X-CHALLENGER");
- }
-
- // does id exist in memory, if so just replace state data
- if(challengers.inMemory(xChallengerGuid)){
- ChallengerAuthData existingChallenger = challengers.getChallenger(xChallengerGuid);
- existingChallenger.fromData(challenger, challengeDefinitions.getDefinedChallenges());
- result.status(200);
- result.header("content-type", "application/json");
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
- return existingChallenger.asJson();
- }
-
- // need to create a new challenger in memory with this state
- // but first... have a limit on the number of challengers an IP address can create
- challengers.purgeOldAuthData();
- ipAddressTracker.purgeEmptyIpAddresses(challengers.getChallengerGuids());
- if(ipAddressTracker.hasLimitBeenReachedFor(request.ip())){
- result.status(429);
- result.header("content-type", "application/json");
- result.header("X-CHALLENGER", xChallengerGuid);
- return ApiResponseError.asAppropriate(request.headers("accept"), "Attempted to create too many challengers, wait and try again later.");
- }
-
-
- // create a challenger from the JSON and instantiate the database
- ChallengerAuthData newChallenger = new ChallengerAuthData(challengeDefinitions.getDefinedChallenges()).fromData(challenger, challengeDefinitions.getDefinedChallenges());
- challengers.put(newChallenger);
- thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(challenger.getXChallenger());
-
- // track challenger against IP
- ipAddressTracker.trackAgainstThisIp(request.ip(), xChallengerGuid);
-
-
- result.status(201);
- result.header("content-type", "application/json");
- result.header("X-CHALLENGER", xChallengerGuid);
- return "";
- });
+ put(
+ "/challenger/:id",
+ (request, result) -> {
+ ChallengerAuthData challenger;
+
+ // for which challenger?
+ String xChallengerGuid = request.params("id");
+
+ if (xChallengerGuid == null) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID");
+ }
+
+ if (!single_player_mode) {
+ try {
+ UUID.fromString(xChallengerGuid);
+ } catch (Exception e) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Invalid Challenger GUID " + e.getMessage());
+ }
+ }
+
+ // try and parse payload, fail if nonsense
+ try {
+ challenger = new Gson().fromJson(request.body(), ChallengerAuthData.class);
+ } catch (Exception e) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson(e.getMessage());
+ }
+
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+
+ if (challenger == null) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson("Invalid Payload");
+ }
+
+ // check payload against id
+ if (!challenger.getXChallenger().equals(xChallengerGuid)) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "URL GUID does not match payload X-CHALLENGER");
+ }
+
+ // does id exist in memory, if so just replace state data
+ if (challengers.inMemory(xChallengerGuid)) {
+ ChallengerAuthData existingChallenger =
+ challengers.getChallenger(xChallengerGuid);
+ existingChallenger.fromData(
+ challenger, challengeDefinitions.getDefinedChallenges());
+ result.status(200);
+ result.header("content-type", "application/json");
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ return existingChallenger.asJson();
+ }
+
+ // need to create a new challenger in memory with this state
+ // but first... have a limit on the number of challengers an IP address can
+ // create
+ challengers.purgeOldAuthData();
+ ipAddressTracker.purgeEmptyIpAddresses(challengers.getChallengerGuids());
+ if (ipAddressTracker.hasLimitBeenReachedFor(request.ip())) {
+ result.status(429);
+ result.header("content-type", "application/json");
+ result.header("X-CHALLENGER", xChallengerGuid);
+ return ApiResponseError.asAppropriate(
+ request.headers("accept"),
+ "Attempted to create too many challengers, wait and try again later.");
+ }
+
+ // create a challenger from the JSON and instantiate the database
+ ChallengerAuthData newChallenger =
+ new ChallengerAuthData(challengeDefinitions.getDefinedChallenges())
+ .fromData(
+ challenger,
+ challengeDefinitions.getDefinedChallenges());
+ challengers.put(newChallenger);
+ thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(
+ challenger.getXChallenger());
+
+ // track challenger against IP
+ ipAddressTracker.trackAgainstThisIp(request.ip(), xChallengerGuid);
+
+ result.status(201);
+ result.header("content-type", "application/json");
+ result.header("X-CHALLENGER", xChallengerGuid);
+ return "";
+ });
// Document the endpoint
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.PUT,
- "/challenger/:guid",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("Restore a saved challenger matching the supplied X-CHALLENGER guid to allow continued tracking of challenges.")
- .addPossibleStatuses(200,201,400).
- addRequestUrlParam(guidField));
-
+ RoutingVerb.PUT,
+ "/challenger/:guid",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "Restore a saved challenger matching the supplied X-CHALLENGER guid to allow continued tracking of challenges.")
+ .addPossibleStatuses(200, 201, 400)
+ .addRequestUrlParam(guidField));
/*
- / challenger
+ / challenger
- */
+ */
- SimpleSparkRouteCreator.addHandler("/challenger", "options", (request, result) ->{
- result.status(204);
- // disallow POST, DELETE, PATCH, TRACE
- result.header("Allow", "POST, OPTIONS");
- return "";
- });
+ SimpleSparkRouteCreator.addHandler(
+ "/challenger",
+ "options",
+ (request, result) -> {
+ result.status(204);
+ // disallow POST, DELETE, PATCH, TRACE
+ result.header("Allow", "POST, OPTIONS");
+ return "";
+ });
// create a challenger
- post("/challenger", (request, result) -> {
-
- if(single_player_mode){
- XChallengerHeader.setResultHeaderBasedOnChallenger(result, challengers.SINGLE_PLAYER.getXChallenger());
- result.raw().setHeader("Location", "/gui/challenges");
- result.status(201);
- return "";
- }
-
- String xChallengerGuid = request.headers("X-CHALLENGER");
- if(xChallengerGuid == null || xChallengerGuid.trim().isEmpty()){
- // create a new challenger with a database
- final ChallengerAuthData challenger = challengers.createNewChallenger();
- // create the database for the user
- thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(challenger.getXChallenger());
- XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
- result.raw().setHeader("Location", "/gui/challenges/" + challenger.getXChallenger());
- result.status(201);
- }else {
- ChallengerAuthData challenger = challengers.getChallenger(xChallengerGuid);
- if(challenger==null){
- // if X-CHALLENGER header exists, and is not a known UUID,
- // return 404, challenger ID not valid
- result.status(404);
- }else{
- // challenger already exists, ensure the database does
- // create the database for the user
- thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(challenger.getXChallenger());
- // if X-CHALLENGER header exists, and has a valid UUID, and UUID exists, then return 200
- result.raw().setHeader("Location", "/gui/challenges/" + challenger.getXChallenger());
- result.status(200);
- }
- XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
- }
- return "";
- });
+ post(
+ "/challenger",
+ (request, result) -> {
+ if (single_player_mode) {
+ XChallengerHeader.setResultHeaderBasedOnChallenger(
+ result, challengers.SINGLE_PLAYER.getXChallenger());
+ result.raw().setHeader("Location", "/gui/challenges");
+ result.status(201);
+ return "";
+ }
+
+ String xChallengerGuid = request.headers("X-CHALLENGER");
+ if (xChallengerGuid == null || xChallengerGuid.trim().isEmpty()) {
+ // create a new challenger with a database
+ final ChallengerAuthData challenger = challengers.createNewChallenger();
+ // create the database for the user
+ thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(
+ challenger.getXChallenger());
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ result.raw()
+ .setHeader(
+ "Location",
+ "/gui/challenges/" + challenger.getXChallenger());
+ result.status(201);
+ } else {
+ ChallengerAuthData challenger = challengers.getChallenger(xChallengerGuid);
+ if (challenger == null) {
+ // if X-CHALLENGER header exists, and is not a known UUID,
+ // return 404, challenger ID not valid
+ result.status(404);
+ } else {
+ // challenger already exists, ensure the database does
+ // create the database for the user
+ thingifier.ensureCreatedAndPopulatedInstanceDatabaseNamed(
+ challenger.getXChallenger());
+ // if X-CHALLENGER header exists, and has a valid UUID, and UUID exists,
+ // then return 200
+ result.raw()
+ .setHeader(
+ "Location",
+ "/gui/challenges/" + challenger.getXChallenger());
+ result.status(200);
+ }
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+ }
+ return "";
+ });
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.POST,
- "/challenger",
- RoutingStatus.returnedFromCall(),
- null).
- addDocumentation("Create a challenger using the X-CHALLENGER guid header.").
- addPossibleStatuses(200,400,405)
- );
+ RoutingVerb.POST,
+ "/challenger",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation("Create a challenger using the X-CHALLENGER guid header.")
+ .addPossibleStatuses(200, 400, 405));
SimpleSparkRouteCreator.routeStatusWhenNot(405, "/challenger", List.of("post", "options"));
/*
- The todos restore endpoint
- */
+ The todos restore endpoint
+ */
// Document the endpoint
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.GET,
- "/challenger/database/:guid",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("Get the todo data for the supplied X-CHALLENGER guid to allow later restoration of the todos.")
- .addPossibleStatuses(200,400,404).
- addRequestUrlParam(guidField));
-
- Route getChallengerDatabaseId = (request, result) -> {
- ChallengerAuthData challenger;
-
- // for which challenger?
- String xChallengerGuid = request.params("id");
-
- if(xChallengerGuid==null){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID");
- }
-
- if(!single_player_mode) {
- try {
- UUID.fromString(xChallengerGuid);
- } catch (Exception e) {
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID " + e.getMessage());
- }
- }
-
- challenger = challengers.getChallenger(xChallengerGuid);
- XChallengerHeader.setResultHeaderBasedOnChallenger(result,challenger);
-
- if(challenger!=null){
- challenger.touch();
- result.header("content-type", "application/json");
-
- ERInstanceData instanceData = challengers.getErModel().getInstanceData(xChallengerGuid);
- if(instanceData==null){
- result.status(404);
- return ApiResponseAsJson.getErrorMessageJson("Challenger database not instantiated " + xChallengerGuid);
- }
-
- result.status(200);
- return challengers.getErModel().getInstanceData(xChallengerGuid).asJson();
- }else{
- result.status(404);
- return ApiResponseAsJson.getErrorMessageJson("Challenger not found " + xChallengerGuid);
- }
- };
-
- SimpleSparkRouteCreator.addHandler("/challenger/database/:id", "options", (request, result) ->{
- result.status(204);
- // disallow POST, DELETE, PATCH, TRACE
- result.header("Allow", "GET, PUT, HEAD, OPTIONS");
- return "";
- });
-
- SimpleSparkRouteCreator.routeStatusWhenNot(405, "/challenger/database/:id", List.of("get", "put", "head", "options"));
+ RoutingVerb.GET,
+ "/challenger/database/:guid",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "Get the todo data for the supplied X-CHALLENGER guid to allow later restoration of the todos.")
+ .addPossibleStatuses(200, 400, 404)
+ .addRequestUrlParam(guidField));
+
+ Route getChallengerDatabaseId =
+ (request, result) -> {
+ ChallengerAuthData challenger;
+
+ // for which challenger?
+ String xChallengerGuid = request.params("id");
+
+ if (xChallengerGuid == null) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID");
+ }
+
+ if (!single_player_mode) {
+ try {
+ UUID.fromString(xChallengerGuid);
+ } catch (Exception e) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Invalid Challenger GUID " + e.getMessage());
+ }
+ }
+
+ challenger = challengers.getChallenger(xChallengerGuid);
+ XChallengerHeader.setResultHeaderBasedOnChallenger(result, challenger);
+
+ if (challenger != null) {
+ challenger.touch();
+ result.header("content-type", "application/json");
+
+ if (challengers.getErModel().getStore(xChallengerGuid) == null) {
+ result.status(404);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Challenger database not instantiated " + xChallengerGuid);
+ }
+
+ result.status(200);
+ return challengers.getErModel().exportInstanceDataAsJson(xChallengerGuid);
+ } else {
+ result.status(404);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Challenger not found " + xChallengerGuid);
+ }
+ };
+
+ SimpleSparkRouteCreator.addHandler(
+ "/challenger/database/:id",
+ "options",
+ (request, result) -> {
+ result.status(204);
+ // disallow POST, DELETE, PATCH, TRACE
+ result.header("Allow", "GET, PUT, HEAD, OPTIONS");
+ return "";
+ });
+
+ SimpleSparkRouteCreator.routeStatusWhenNot(
+ 405, "/challenger/database/:id", List.of("get", "put", "head", "options"));
// add a GET challenger/database/:id in the proper database format
- get("/challenger/database/:id", (request, result) -> {
- return getChallengerDatabaseId.handle(request, result);
- });
-
- head("/challenger/database/:id", (request, result) -> {
- getChallengerDatabaseId.handle(request, result);
- return "";
- });
+ get(
+ "/challenger/database/:id",
+ (request, result) -> {
+ return getChallengerDatabaseId.handle(request, result);
+ });
+ head(
+ "/challenger/database/:id",
+ (request, result) -> {
+ getChallengerDatabaseId.handle(request, result);
+ return "";
+ });
// endpoint to restore a saved challenger database from UI
- put("/challenger/database/:id", (request, result) -> {
-
- String xChallengerGuid = request.params("id");
-
- // challenger uuid must exist and be valid
- if(xChallengerGuid==null){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID");
- }
-
- if(!single_player_mode) {
- try {
- UUID.fromString(xChallengerGuid);
- } catch (Exception e) {
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID " + e.getMessage());
- }
- }
-
- if(!challengers.inMemory(xChallengerGuid)){
- result.status(404);
- return ApiResponseAsJson.getErrorMessageJson("Unknown Challenger GUID - have you created or loaded the challenger state");
- }
-
- // thingifier allows loading database from json extract
- try {
- thingifier.ensureCreatedAndPopulatedInstanceDatabaseFromJson(xChallengerGuid.trim(), request.body());
- }catch (Exception e){
- result.status(400);
- return ApiResponseAsJson.getErrorMessageJson(e.getMessage());
- }
-
-
- if(challengers.getErModel().getDatabaseNames().contains(xChallengerGuid)){
- result.status(204);
- result.header("content-type", "application/json");
- result.header("X-CHALLENGER", xChallengerGuid);
- return "";
- }else{
- result.status(500);
- return ApiResponseAsJson.getErrorMessageJson("Unknown error, database not found");
- }
- });
+ put(
+ "/challenger/database/:id",
+ (request, result) -> {
+ String xChallengerGuid = request.params("id");
+
+ // challenger uuid must exist and be valid
+ if (xChallengerGuid == null) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson("Invalid Challenger GUID");
+ }
+
+ if (!single_player_mode) {
+ try {
+ UUID.fromString(xChallengerGuid);
+ } catch (Exception e) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Invalid Challenger GUID " + e.getMessage());
+ }
+ }
+
+ if (!challengers.inMemory(xChallengerGuid)) {
+ result.status(404);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Unknown Challenger GUID - have you created or loaded the challenger state");
+ }
+
+ // thingifier allows loading database from json extract
+ try {
+ thingifier.ensureCreatedAndPopulatedInstanceDatabaseFromJson(
+ xChallengerGuid.trim(), request.body());
+ } catch (Exception e) {
+ result.status(400);
+ return ApiResponseAsJson.getErrorMessageJson(e.getMessage());
+ }
+
+ if (challengers.getErModel().getDatabaseNames().contains(xChallengerGuid)) {
+ result.status(204);
+ result.header("content-type", "application/json");
+ result.header("X-CHALLENGER", xChallengerGuid);
+ return "";
+ } else {
+ result.status(500);
+ return ApiResponseAsJson.getErrorMessageJson(
+ "Unknown error, database not found");
+ }
+ });
// Document the endpoint
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.PUT,
- "/challenger/database/:guid",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("Restore a saved set of todos for a challenger matching the supplied X-CHALLENGER guid.").
- addPossibleStatuses(204,400).
- addRequestUrlParam(guidField)
- );
-
+ RoutingVerb.PUT,
+ "/challenger/database/:guid",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation(
+ "Restore a saved set of todos for a challenger matching the supplied X-CHALLENGER guid.")
+ .addPossibleStatuses(204, 400)
+ .addRequestUrlParam(guidField));
// TODO: add a protected admin page with an environment variable protection as password
}
-
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengesRoutes.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengesRoutes.java
index 45ecfec3..13cd606e 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengesRoutes.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/ChallengesRoutes.java
@@ -1,85 +1,113 @@
package uk.co.compendiumdev.challenge.challengesrouting;
+import static spark.Spark.*;
+
+import java.util.List;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
import uk.co.compendiumdev.challenge.apimodel.ChallengeThingifier;
import uk.co.compendiumdev.challenge.challengers.Challengers;
import uk.co.compendiumdev.challenge.challenges.ChallengeDefinitions;
-import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
-import uk.co.compendiumdev.thingifier.api.restapihandlers.RestApiGetHandler;
import uk.co.compendiumdev.thingifier.api.docgen.RoutingDefinition;
import uk.co.compendiumdev.thingifier.api.docgen.RoutingStatus;
import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
+import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
+import uk.co.compendiumdev.thingifier.api.restapihandlers.RestApiGetHandler;
import uk.co.compendiumdev.thingifier.application.AdhocDocumentedSparkRouteConfigurer;
import uk.co.compendiumdev.thingifier.application.routehandlers.SparkApiRequestResponseHandler;
import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
import uk.co.compendiumdev.thingifier.core.query.FilterBy;
import uk.co.compendiumdev.thingifier.core.query.QueryFilterParams;
import uk.co.compendiumdev.thingifier.spark.SimpleSparkRouteCreator;
-import java.util.List;
-
-import static spark.Spark.*;
public class ChallengesRoutes {
- public void configure(final Challengers challengers, final boolean single_player_mode,
- final ThingifierApiDocumentationDefn apiDefn,
- final ChallengeDefinitions challengeDefinitions){
-
-
- get("/challenges", (request, result) -> {
-
- ChallengerAuthData challenger = challengers.getChallenger(request.headers("X-CHALLENGER"));
-
- if(!single_player_mode){
- if(challenger!=null){
- result.raw().setHeader("Location", "/gui/challenges/" + challenger.getXChallenger());
- }
- }else{
- result.raw().setHeader("Location", "/gui/challenges");
- }
-
- // Todo: use the cloneThingifierWithNewData here and simplify the ChallengeThingifier
- ChallengeThingifier challengeThingifier = new ChallengeThingifier();
- final EntityDefinition challengeDefn = challengeThingifier.challengeDefn;
- challengeThingifier.populateThingifierFrom(challengeDefinitions);
-
- return new SparkApiRequestResponseHandler(request, result, challengeThingifier.challengeThingifier).
- usingHandler((anHttpApiRequest)->{
- challengeThingifier.populateThingifierFromStatus(challenger);
- QueryFilterParams queryParams = anHttpApiRequest.getFilterableQueryParams();
- //final Map queryParams = anHttpApiRequest.getQueryParams();
- if(!queryParams.hasSortBy()){
- queryParams.add(new FilterBy("sort_by","+ID"));
+ public void configure(
+ final Challengers challengers,
+ final boolean single_player_mode,
+ final ThingifierApiDocumentationDefn apiDefn,
+ final ChallengeDefinitions challengeDefinitions) {
+
+ get(
+ "/challenges",
+ (request, result) -> {
+ ChallengerAuthData challenger =
+ challengers.getChallenger(request.headers("X-CHALLENGER"));
+
+ if (!single_player_mode) {
+ if (challenger != null) {
+ result.raw()
+ .setHeader(
+ "Location",
+ "/gui/challenges/" + challenger.getXChallenger());
}
-// if(!queryParams.containsKey("sortBy") &&
-// !queryParams.containsKey("sort_by")){
-// // force a sort
-// queryParams.put("sort_by","+ID");
-// }
- return new RestApiGetHandler(challengeThingifier.challengeThingifier)
- .handle(challengeDefn.getPlural(),
- queryParams, anHttpApiRequest.getHeaders());
- }).handle();
-
- });
+ } else {
+ result.raw().setHeader("Location", "/gui/challenges");
+ }
+
+ // Todo: use the cloneThingifierWithNewData here and simplify the
+ // ChallengeThingifier
+ ChallengeThingifier challengeThingifier = new ChallengeThingifier();
+ final EntityDefinition challengeDefn = challengeThingifier.challengeDefn;
+ challengeThingifier.populateThingifierFrom(challengeDefinitions);
+
+ return new SparkApiRequestResponseHandler(
+ request, result, challengeThingifier.challengeThingifier)
+ .usingHandler(
+ (anHttpApiRequest) -> {
+ challengeThingifier.populateThingifierFromStatus(
+ challenger);
+ QueryFilterParams queryParams =
+ anHttpApiRequest.getFilterableQueryParams();
+ // final Map queryParams =
+ // anHttpApiRequest.getQueryParams();
+ if (!queryParams.hasSortBy()) {
+ queryParams.add(new FilterBy("sort_by", "+ID"));
+ }
+ //
+ // if(!queryParams.containsKey("sortBy") &&
+ //
+ // !queryParams.containsKey("sort_by")){
+ // // force a sort
+ //
+ // queryParams.put("sort_by","+ID");
+ // }
+ return new RestApiGetHandler(
+ challengeThingifier.challengeThingifier)
+ .handle(
+ challengeDefn.getPlural(),
+ queryParams,
+ anHttpApiRequest.getHeaders());
+ })
+ .handle();
+ });
apiDefn.addRouteToDocumentation(
new RoutingDefinition(
- RoutingVerb.GET,
+ RoutingVerb.GET,
+ "/challenges",
+ RoutingStatus.returnedFromCall(),
+ null)
+ .addDocumentation("Get list of challenges and their completion status")
+ .addPossibleStatuses(200));
+
+ // TODO: because these hardcode contentType and ignore Accept there should be a light weight
+ // wrapper available
+ new AdhocDocumentedSparkRouteConfigurer(apiDefn)
+ .add(
"/challenges",
- RoutingStatus.returnedFromCall(),
- null).addDocumentation("Get list of challenges and their completion status").
- addPossibleStatuses(200));
-
- // TODO: because these hardcode contentType and ignore Accept there should be a light weight wrapper available
- new AdhocDocumentedSparkRouteConfigurer(apiDefn).
- add("/challenges", RoutingVerb.HEAD, 200, "Headers for list of challenges endpoint",
- (request, result) ->{
+ RoutingVerb.HEAD,
+ 200,
+ "Headers for list of challenges endpoint",
+ (request, result) -> {
result.status(200);
result.type("application/json");
return "";
- }).
- add("/challenges", RoutingVerb.OPTIONS, 200,"Options for list of challenges endpoint",
+ })
+ .add(
+ "/challenges",
+ RoutingVerb.OPTIONS,
+ 200,
+ "Options for list of challenges endpoint",
((request, result) -> {
result.status(200);
result.type("application/json");
@@ -87,8 +115,7 @@ public void configure(final Challengers challengers, final boolean single_player
return "";
}));
- SimpleSparkRouteCreator.routeStatusWhenNot(405, "/challenges", List.of("get", "head", "options"));
-
+ SimpleSparkRouteCreator.routeStatusWhenNot(
+ 405, "/challenges", List.of("get", "head", "options"));
}
-
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/HeartBeatRoutes.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/HeartBeatRoutes.java
index 62e89d58..6b3eddb1 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/HeartBeatRoutes.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/HeartBeatRoutes.java
@@ -1,44 +1,39 @@
package uk.co.compendiumdev.challenge.challengesrouting;
-import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
+import java.util.List;
import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
+import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
import uk.co.compendiumdev.thingifier.application.AdhocDocumentedSparkRouteConfigurer;
import uk.co.compendiumdev.thingifier.spark.SimpleSparkRouteCreator;
-import java.util.List;
-
public class HeartBeatRoutes {
public void configure(final ThingifierApiDocumentationDefn apiDefn) {
- String endpoint ="/heartbeat";
+ String endpoint = "/heartbeat";
final AdhocDocumentedSparkRouteConfigurer sparkRouteConfig =
new AdhocDocumentedSparkRouteConfigurer(apiDefn);
- sparkRouteConfig.add(endpoint,
- RoutingVerb.GET, 204,
- "Is the server running? YES 204");
+ sparkRouteConfig.add(endpoint, RoutingVerb.GET, 204, "Is the server running? YES 204");
- sparkRouteConfig.add(endpoint,
- RoutingVerb.HEAD, 204,
- "Headers for heartbeat endpoint");
+ sparkRouteConfig.add(endpoint, RoutingVerb.HEAD, 204, "Headers for heartbeat endpoint");
- sparkRouteConfig.add(endpoint,
- RoutingVerb.OPTIONS, 204,
+ sparkRouteConfig.add(
+ endpoint,
+ RoutingVerb.OPTIONS,
+ 204,
"Options for heartbeat endpoint",
(request, result) -> {
result.status(204);
result.header("Allow", "GET, HEAD, OPTIONS");
return "";
- }
- );
+ });
// undocumented handlers
SimpleSparkRouteCreator simpleSparkRouteCreator = new SimpleSparkRouteCreator(endpoint);
- simpleSparkRouteCreator.status(405, List.of("post", "delete", "put"));
- simpleSparkRouteCreator.status(500, List.of("patch"));
- simpleSparkRouteCreator.status(501, List.of("trace"));
-
+ simpleSparkRouteCreator.status(405, true, List.of("post", "delete", "put"));
+ simpleSparkRouteCreator.status(500, true, List.of("patch"));
+ simpleSparkRouteCreator.status(501, true, List.of("trace"));
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/XChallengerHeader.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/XChallengerHeader.java
index 84297d98..78160646 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/XChallengerHeader.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challengesrouting/XChallengerHeader.java
@@ -2,56 +2,57 @@
import spark.Response;
import uk.co.compendiumdev.challenge.ChallengerAuthData;
-import uk.co.compendiumdev.thingifier.application.internalhttpconversion.InternalHttpResponse;
+import uk.co.compendiumdev.thingifier.application.internalhttp.InternalHttpResponse;
public class XChallengerHeader {
- public static final String NOT_FOUND_ERROR_MESSAGE = "UNKNOWN CHALLENGER - Challenger not found";
+ public static final String NOT_FOUND_ERROR_MESSAGE =
+ "UNKNOWN CHALLENGER - Challenger not found";
- public static void setResultHeaderBasedOnChallenger(final Response result,
- final ChallengerAuthData challenger) {
+ public static void setResultHeaderBasedOnChallenger(
+ final Response result, final ChallengerAuthData challenger) {
- if(result==null){
+ if (result == null) {
return;
}
- if(challenger==null){
+ if (challenger == null) {
result.raw().setHeader("X-CHALLENGER", NOT_FOUND_ERROR_MESSAGE);
- //result.raw().setHeader("X-CHALLENGER", "Challenger not recognised");
- }else{
- result.raw().setHeader("X-CHALLENGER",challenger.getXChallenger());
+ // result.raw().setHeader("X-CHALLENGER", "Challenger not recognised");
+ } else {
+ result.raw().setHeader("X-CHALLENGER", challenger.getXChallenger());
}
}
- public static void setResultHeaderBasedOnChallenger(final Response result,
- final String challengerGUID) {
+ public static void setResultHeaderBasedOnChallenger(
+ final Response result, final String challengerGUID) {
- if(result==null){
+ if (result == null) {
return;
}
- if(challengerGUID==null || challengerGUID.trim().length()==0){
+ if (challengerGUID == null || challengerGUID.trim().length() == 0) {
result.raw().setHeader("X-CHALLENGER", NOT_FOUND_ERROR_MESSAGE);
- //result.raw().setHeader("X-CHALLENGER", "Challenger not recognised");
- }else{
- result.raw().setHeader("X-CHALLENGER",challengerGUID);
+ // result.raw().setHeader("X-CHALLENGER", "Challenger not recognised");
+ } else {
+ result.raw().setHeader("X-CHALLENGER", challengerGUID);
}
}
- public static void setResultHeaderBasedOnChallenger(final InternalHttpResponse response,
- final ChallengerAuthData challenger) {
+ public static void setResultHeaderBasedOnChallenger(
+ final InternalHttpResponse response, final ChallengerAuthData challenger) {
- if(response==null){
+ if (response == null) {
return;
}
- if(response.getHeaders().headerExists("X-CHALLENGER")){
+ if (response.getHeaders().headerExists("X-CHALLENGER")) {
return;
}
- if(challenger==null){
+ if (challenger == null) {
response.setHeader("X-CHALLENGER", NOT_FOUND_ERROR_MESSAGE);
- }else{
+ } else {
response.setHeader("X-CHALLENGER", challenger.getXChallenger());
}
}
diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java
index 76de136d..e9b8f0c3 100644
--- a/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java
+++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java
@@ -1,5 +1,9 @@
package uk.co.compendiumdev.challenge.gui;
+import static spark.Spark.*;
+
+import java.time.LocalDate;
+import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Request;
@@ -12,11 +16,6 @@
import uk.co.compendiumdev.thingifier.core.EntityRelModel;
import uk.co.compendiumdev.thingifier.htmlgui.htmlgen.DefaultGUIHTML;
-import java.time.LocalDate;
-import java.util.*;
-
-import static spark.Spark.*;
-
public class ChallengerWebGUI {
private static final LocalDate SEO_FIXED_LASTMOD = LocalDate.parse("2026-02-18");
@@ -31,16 +30,17 @@ public ChallengerWebGUI(final DefaultGUIHTML defaultGui, final boolean guiStayAl
this.pageNotFoundHtmlResponse = new PageNotFoundResponse(guiManagement);
}
- String getChallengesPageHtmlHeader(){
+ String getChallengesPageHtmlHeader() {
return guiManagement.getPageStart(
"API Challenges - Improve your API Skills",
- "" +
- "",
+ ""
+ + "",
"/gui/challenges");
}
- String getSponsorMessage(){
- String sponsorMessage = """
+ String getSponsorMessage() {
+ String sponsorMessage =
+ """
Support this site by joining our Patreon.
@@ -54,18 +54,20 @@ String getSponsorMessage(){
return sponsorMessage;
}
- public void setup(final Challengers challengers,
- final ChallengeDefinitions challengeDefinitions,
- final PersistenceLayer persistenceLayer,
- final boolean single_player_mode) {
+ public void setup(
+ final Challengers challengers,
+ final ChallengeDefinitions challengeDefinitions,
+ final PersistenceLayer persistenceLayer,
+ final boolean single_player_mode) {
guiManagement.appendMenuItem("Home", "/");
guiManagement.appendMenuItem("Entities Explorer", "/gui/entities");
guiManagement.appendMenuItem("Challenges", "/gui/challenges");
- guiManagement.appendMenuItem("API documentation","/docs");
+ guiManagement.appendMenuItem("API documentation", "/docs");
guiManagement.appendMenuItem("Learning", "/learning");
- String actualMenu = """
+ String actualMenu =
+ """
");
- return "";
- });
+ // if no inmemory challenger then ask the persistence layer
+ if (challenger == null) {
+ persistence =
+ persistenceLayer.tryToLoadChallenger(challengers, xChallenger);
+ }
+ }
- after((request, response)->{
+ if (challenger == null) {
+ String persistenceReason = "";
+ if (persistence != null) {
+ persistenceReason = persistence.getErrorMessage();
+ }
+ html.append("
Use the Descriptions of the challenges below to explore the API and solve the challenges." +
- " Remember to use the API documentation to see the format of POST requests.
" +
- "
Progress, and the TODOs database content can be saved to, and restored from, LocalStorage in the browser - or managed via the API." +
- "
");
+ html.append(
+ "
Use the Descriptions of the challenges below to explore the API and solve the challenges."
+ + " Remember to use the API documentation to see the format of POST requests.
"
+ + "
Progress, and the TODOs database content can be saved to, and restored from, LocalStorage in the browser - or managed via the API."
+ + "
");
html.append("
");
return html.toString();
}
- private String multiUserShortHelp(Boolean canSaveToPersistence, boolean canRestoreFromPersistence) {
+ private String multiUserShortHelp(
+ Boolean canSaveToPersistence, boolean canRestoreFromPersistence) {
final StringBuilder html = new StringBuilder();
html.append("
");
- html.append("
To view your challenges status in multi-user mode, make sure you have registered as a challenger using a `POST` request to `/challenger` and are including an `X-CHALLENGER` header in all your requests.
");
- html.append("
Then view the challenges in the GUI by visiting `/gui/challenges/{GUID}`, where `{GUID}` is the value in the `X-CHALLENGER` header.
");
- html.append("
Challenger sessions are purged from the server memory after 10 minutes of inactivity.
To view your challenges status in multi-user mode, make sure you have registered as a challenger using a `POST` request to `/challenger` and are including an `X-CHALLENGER` header in all your requests.
");
+ html.append(
+ "
Then view the challenges in the GUI by visiting `/gui/challenges/{GUID}`, where `{GUID}` is the value in the `X-CHALLENGER` header.
");
+ html.append(
+ "
Challenger sessions are purged from the server memory after 10 minutes of inactivity.
");
+ if (canSaveToPersistence) {
html.append("Challenger progress is configured to saved on the server.
");
- }else{
- html.append("Challenger progress is not configured to automatically save on the server. Use the GUI or UI to save progress locally.
");
+ } else {
+ html.append(
+ "Challenger progress is not configured to automatically save on the server. Use the GUI or UI to save progress locally.
");
}
- if(canRestoreFromPersistence) {
- html.append("To restore a previously saved session progress from the server, issue an API request with the X-CHALLENGER header (note this will restore the completion state of challenges, but not the data you were using).
");
+ if (canRestoreFromPersistence) {
+ html.append(
+ "To restore a previously saved session progress from the server, issue an API request with the X-CHALLENGER header (note this will restore the completion state of challenges, but not the data you were using).
");
}
- html.append("
Session state and current todo list can be stored to local storage, and later restored using the GUI buttons or via API.
");
html.append("");
}
@@ -434,8 +508,8 @@ public String getHtmlVersionOfMarkdownContent(String contentFolder, String conte
}
// TODO: move this into a markdown file so it can be cached and amended easily
- private String dropDownMenuAsMarkdown(){
- if(sideMenuText.isEmpty()){
+ private String dropDownMenuAsMarkdown() {
+ if (sideMenuText.isEmpty()) {
sideMenuText = getResourceAsString("partials/content-index.md");
}
@@ -443,128 +517,143 @@ private String dropDownMenuAsMarkdown(){
}
// TODO: improve the macro parsing
- // TODO: add a variables macro so we can set variables like schemeHost (http://localhost:4567) and replace variables in the docs - should add a 'default.varname': parsing in the markdown, would allow showing the 'proper url' regardless of environment hosting
+ // TODO: add a variables macro so we can set variables like schemeHost (http://localhost:4567)
+ // and replace variables in the docs - should add a 'default.varname': parsing in the markdown,
+ // would allow showing the 'proper url' regardless of environment hosting
/*
- Macros are added to the markdown with the following syntax {{}}
- e.g. {{}}
- Some of these macros are direct string replacement injection from the params map
- Others like youtube-embed have been hard coded here
+ Macros are added to the markdown with the following syntax {{}}
+ e.g. {{}}
+ Some of these macros are direct string replacement injection from the params map
+ Others like youtube-embed have been hard coded here
- */
+ */
private String processMacrosInContentLine(String line, Map params) {
- if(!line.contains("{{<"))
- return line;
-
+ if (!line.contains("{{<")) return line;
-// String youTubeHtmlBlock = """
-//