Skip to content

Commit a87bc72

Browse files
committed
release ready
bumped thingifier to 1.5.3 for release, removed the static index.html in preference to the dynamic GUI version, with a redirect from "/" to "/gui". And this includes the challenger app.
1 parent 9cddc61 commit a87bc72

File tree

23 files changed

+335
-93
lines changed

23 files changed

+335
-93
lines changed

challenger/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<modelVersion>4.0.0</modelVersion>
1111

1212
<artifactId>challenger</artifactId>
13-
<version>${thingifier.version}</version>
13+
<version>0.1</version>
1414

1515
<dependencies>
1616
<dependency>
@@ -47,11 +47,11 @@
4747
<descriptorRef>jar-with-dependencies</descriptorRef>
4848
</descriptorRefs>
4949
<!-- rename the 'full' jar -->
50-
<finalName>runTodoListRestAPI-${project.version}</finalName>
50+
<finalName>apichallenges</finalName>
5151
<appendAssemblyId>false</appendAssemblyId>
5252
<archive>
5353
<manifest>
54-
<mainClass>uk.co.compendiumdev.todolist.application.Main</mainClass>
54+
<mainClass>uk.co.compendiumdev.challenge.Main</mainClass>
5555
</manifest>
5656
</archive>
5757
</configuration>

challenger/readme.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# API Self Teaching Challenges
2+
3+
From the commandline, run the app:
4+
5+
- `java -jar apichallenges.jar`
6+
7+
This will start an API and GUI running on:
8+
9+
- http://localhost:4567
10+
11+
If you visit the URL in a browser then you will see the GUI where you can:
12+
13+
- read the API reference documentation
14+
- browse the items in the application
15+
- see the API Challenges
16+
17+
## Challenges
18+
19+
The challenges can be completed by issuing API requests to the API.
20+
21+
e.g. `GET http://localhost:4567/todos` would complete the challenge to "GET the list of todos"
22+
23+
You can also `GET http://localhost:4567/challenges` to get the list of challenges and their status as an API call.
24+
25+
26+

challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeApiModel.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public Thingifier get() {
4141
ThingInstance filework = todo.createInstance().setValue("title", "file paperwork");
4242
todo.addInstance(filework);
4343

44+
todoList.apiConfig().setResponsesToShowGuids(false);
45+
4446
return todoList;
4547
}
4648
}

challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
package uk.co.compendiumdev.challenge;
22

3-
import com.google.gson.Gson;
3+
import uk.co.compendiumdev.thingifier.Thingifier;
44
import uk.co.compendiumdev.thingifier.api.routings.RoutingDefinition;
55
import uk.co.compendiumdev.thingifier.api.routings.RoutingStatus;
66
import uk.co.compendiumdev.thingifier.api.routings.RoutingVerb;
77
import uk.co.compendiumdev.thingifier.application.ThingifierRestServer;
8-
import uk.co.compendiumdev.thingifier.application.routehandlers.ShutdownRouteHandler;
8+
import uk.co.compendiumdev.thingifier.htmlgui.DefaultGUIHTML;
99

10-
import java.util.ArrayList;
11-
import java.util.HashMap;
12-
import java.util.List;
13-
import java.util.Map;
10+
import java.util.*;
1411

1512
import static spark.Spark.get;
1613

1714
public class ChallengeRouteHandler {
15+
private final Thingifier thingifier;
1816
List<RoutingDefinition> routes;
1917
Challenges challenges;
2018

21-
public ChallengeRouteHandler(){
19+
public ChallengeRouteHandler(Thingifier thingifier){
2220
routes = new ArrayList();
2321
challenges = new Challenges();
22+
this.thingifier = thingifier;
2423
}
2524

2625
public List<RoutingDefinition> getRoutes(){
@@ -46,7 +45,59 @@ public ChallengeRouteHandler configureRoutes() {
4645

4746
public void addHooks(final ThingifierRestServer restServer) {
4847

48+
restServer.registerPreRequestHook(new ChallengerSparkHTTPRequestHook(challenges));
4949
restServer.registerHttpApiRequestHook(new ChallengerApiRequestHook(challenges));
50-
restServer.registerHttpApiResponseHook(new ChallengerApiResponseHook(challenges));
50+
restServer.registerHttpApiResponseHook(new ChallengerApiResponseHook(challenges, thingifier));
51+
}
52+
53+
public void setupGui(DefaultGUIHTML guiManagement) {
54+
guiManagement.addMenuItem("Challenges", "/gui/challenges");
55+
56+
guiManagement.setHomePageContent(" <h2 id=\"challenges\">Challenges</h2>\n" +
57+
" <p>The challenges can be completed by issuing API requests to the API.</p>\n" +
58+
" <p>e.g. <code>GET http://localhost:4567/todos</code> would complete the challenge to &quot;GET the list of todos&quot;</p>\n" +
59+
" <p>You can also <code>GET http://localhost:4567/challenges</code> to get the list of challenges and their status as an API call. </p>\n"
60+
);
61+
62+
get("/", (request, result) -> {
63+
result.redirect("/gui");
64+
return "";
65+
});
66+
67+
get("/gui/challenges", (request, result) -> {
68+
result.type("text/html");
69+
result.status(200);
70+
StringBuilder html = new StringBuilder();
71+
html.append(guiManagement.getPageStart("Challenges"));
72+
html.append(guiManagement.getMenuAsHTML());
73+
74+
html.append("<table>");
75+
html.append("<thead>");
76+
html.append("<tr>");
77+
78+
html.append("<th>Challenge</th>");
79+
html.append("<th>Done</th>");
80+
html.append("<th>Description</th>");
81+
html.append("</tr>");
82+
html.append("</thead>");
83+
html.append("<tbody>");
84+
85+
for(ChallengeData challenge : challenges.getChallenges()){
86+
html.append("<tr>");
87+
html.append(String.format("<td>%s</td>", challenge.name));
88+
html.append(String.format("<td>%b</td>", challenge.status));
89+
html.append(String.format("<td>%s</td>", challenge.description));
90+
html.append("</tr>");
91+
}
92+
93+
html.append("</tbody>");
94+
html.append("</table>");
95+
96+
html.append(guiManagement.getPageFooter());
97+
html.append(guiManagement.getPageEnd());
98+
return html.toString();
99+
});
100+
101+
51102
}
52103
}

challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengerApiRequestHook.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
66
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiRequestHook;
77

8+
import static uk.co.compendiumdev.challenge.Challenges.CHALLENGE.GET_CHALLENGES;
89
import static uk.co.compendiumdev.challenge.Challenges.CHALLENGE.GET_TODOS;
910

1011
public class ChallengerApiRequestHook implements HttpApiRequestHook {

challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengerApiResponseHook.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
package uk.co.compendiumdev.challenge;
22

3+
import uk.co.compendiumdev.thingifier.Thingifier;
34
import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
45
import uk.co.compendiumdev.thingifier.api.http.HttpApiResponse;
56
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
67
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiResponseHook;
78

9+
import java.util.Collection;
10+
811
import static uk.co.compendiumdev.challenge.Challenges.CHALLENGE.*;
912

1013
public class ChallengerApiResponseHook implements HttpApiResponseHook {
1114

1215
private final Challenges challenges;
16+
private final Thingifier thingifier;
1317

14-
public ChallengerApiResponseHook(final Challenges challenges) {
18+
public ChallengerApiResponseHook(final Challenges challenges, Thingifier thingifier) {
1519
this.challenges = challenges;
20+
this.thingifier = thingifier;
1621
}
1722

1823
@Override
19-
public HttpApiResponse run(final HttpApiRequest request, final HttpApiResponse response, final ThingifierApiConfig config) {
24+
public HttpApiResponse run(final HttpApiRequest request,
25+
final HttpApiResponse response,
26+
final ThingifierApiConfig config) {
2027

28+
// READ
2129
if(request.getVerb() == HttpApiRequest.VERB.GET &&
2230
request.getPath().matches("todos/.*") &&
2331
response.getStatusCode()==200){
@@ -30,8 +38,52 @@ public HttpApiResponse run(final HttpApiRequest request, final HttpApiResponse r
3038
challenges.pass(GET_TODO_404);
3139
}
3240

41+
// CREATE
42+
if(request.getVerb() == HttpApiRequest.VERB.POST &&
43+
request.getPath().matches("todos") &&
44+
response.getStatusCode()==201){
45+
challenges.pass(POST_TODOS);
46+
}
47+
48+
if(request.getVerb() == HttpApiRequest.VERB.POST &&
49+
request.getPath().matches("todos") &&
50+
response.getStatusCode()==400 &&
51+
collate(response.apiResponse().getErrorMessages()).contains(
52+
"Failed Validation: doneStatus should be BOOLEAN")){
53+
challenges.pass(POST_TODOS_BAD_DONE_STATUS);
54+
}
55+
56+
// UPDATE
57+
if(request.getVerb() == HttpApiRequest.VERB.POST &&
58+
request.getPath().matches("todos/.*") &&
59+
response.getStatusCode()==200){
60+
challenges.pass(POST_UPDATE_TODO);
61+
}
62+
63+
64+
// DELETE
65+
if(request.getVerb() == HttpApiRequest.VERB.DELETE &&
66+
request.getPath().matches("todos/.*") &&
67+
response.getStatusCode()==200){
68+
challenges.pass(DELETE_A_TODO);
69+
}
70+
71+
if(request.getVerb() == HttpApiRequest.VERB.DELETE &&
72+
request.getPath().matches("todos/.*") &&
73+
response.getStatusCode()==200 &&
74+
thingifier.getThingWithPluralNamed("todos").countInstances()==0){
75+
challenges.pass(DELETE_ALL_TODOS);
76+
}
3377

3478
// do not interfere with api and return null
3579
return null;
3680
}
81+
82+
String collate(Collection<String> strings){
83+
String collated = "";
84+
for(String string : strings){
85+
collated = collated + " " + string;
86+
}
87+
return collated;
88+
}
3789
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package uk.co.compendiumdev.challenge;
2+
3+
import spark.Request;
4+
import spark.Response;
5+
import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
6+
import uk.co.compendiumdev.thingifier.application.sparkhttpmessageHooks.SparkRequestResponseHook;
7+
8+
import static uk.co.compendiumdev.challenge.Challenges.CHALLENGE.GET_CHALLENGES;
9+
10+
public class ChallengerSparkHTTPRequestHook implements SparkRequestResponseHook {
11+
private final Challenges challenges;
12+
13+
public ChallengerSparkHTTPRequestHook(final Challenges challenges) {
14+
this.challenges = challenges;
15+
}
16+
17+
@Override
18+
public void run(final Request request, final Response response) {
19+
20+
if(request.requestMethod().toUpperCase().contentEquals("GET") &&
21+
request.pathInfo().contentEquals("/challenges")){
22+
challenges.pass(GET_CHALLENGES);
23+
}
24+
25+
}
26+
}

challenger/src/main/java/uk/co/compendiumdev/challenge/Challenges.java

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import com.google.gson.Gson;
44

5-
import java.util.HashMap;
6-
import java.util.Map;
5+
import java.util.*;
76

87
public class Challenges {
98

109
Map<CHALLENGE, ChallengeData> challengeStatus;
10+
List<ChallengeData> orderedChallengeStatus;
1111

1212
public void pass(final CHALLENGE id) {
1313
try{
@@ -17,27 +17,62 @@ public void pass(final CHALLENGE id) {
1717
}
1818
}
1919

20+
public Collection<ChallengeData> getChallenges() {
21+
return orderedChallengeStatus;
22+
}
23+
2024
public enum CHALLENGE{
21-
GET_TODOS, GET_TODO, GET_TODO_404;
25+
GET_TODOS, GET_TODO, GET_TODO_404, POST_TODOS, GET_CHALLENGES, POST_TODOS_BAD_DONE_STATUS, DELETE_A_TODO, DELETE_ALL_TODOS, POST_UPDATE_TODO;
2226
}
2327

2428
public Challenges(){
2529
challengeStatus = new HashMap<>();
30+
orderedChallengeStatus = new ArrayList<>();
31+
32+
/* todo: Basic set of CRUD challenges */
33+
34+
// READ
35+
addChallenge(CHALLENGE.GET_CHALLENGES, "GET /challenges (200)",
36+
"Issue a GET request on the `/challenges` end point");
2637

27-
addChallenge(CHALLENGE.GET_TODOS, "GET /todos",
38+
addChallenge(CHALLENGE.GET_TODOS, "GET /todos (200)",
2839
"Issue a GET request on the `/todos` end point");
29-
addChallenge(CHALLENGE.GET_TODO, "GET /todos/{guid}",
30-
"Issue a GET request on the `/todos/{guid}` end point to return a specific todo");
31-
addChallenge(CHALLENGE.GET_TODO_404, "GET /todos/{guid}",
32-
"Issue a GET request on the `/todos/{guid}` end point for a todo that does not exist");
40+
addChallenge(CHALLENGE.GET_TODO, "GET /todos/{id} (200)",
41+
"Issue a GET request on the `/todos/{id}` end point to return a specific todo");
42+
addChallenge(CHALLENGE.GET_TODO_404, "GET /todos/{id} (404)",
43+
"Issue a GET request on the `/todos/{id}` end point for a todo that does not exist");
44+
45+
// CREATE
46+
addChallenge(CHALLENGE.POST_TODOS, "POST /todos (201)",
47+
"Issue a POST request to successfully create a todo");
48+
addChallenge(CHALLENGE.POST_TODOS_BAD_DONE_STATUS, "POST /todos (400)",
49+
"Issue a POST request to create a todo but fail validation on the `doneStatus` field");
50+
51+
// UPDATE
52+
addChallenge(CHALLENGE.POST_UPDATE_TODO, "POST /todos.{id} (200)",
53+
"Issue a POST request to successfully update a todo");
54+
55+
// DELETE
56+
addChallenge(CHALLENGE.DELETE_A_TODO, "DELETE /todos/{id} (200)",
57+
"Issue a DELETE request to successfully delete a todo");
58+
addChallenge(CHALLENGE.DELETE_ALL_TODOS, "DELETE /todos/{id} (200)",
59+
"Issue a DELETE request to successfully delete the last todo");
3360

61+
// todo: expand out the challenges
62+
// PUT
63+
// OPTIONS
64+
// HEAD
65+
// status code challenges
66+
// method not allowed
3467
}
3568

3669
private void addChallenge(final CHALLENGE id, final String name, final String description) {
37-
challengeStatus.put(id, new ChallengeData( name, description));
70+
ChallengeData challenge = new ChallengeData( name, description);
71+
challengeStatus.put(id, challenge);
72+
orderedChallengeStatus.add(challenge);
3873
}
3974

4075
public String getAsJson(){
41-
return new Gson().toJson(challengeStatus);
76+
return new Gson().toJson(orderedChallengeStatus);
4277
}
4378
}

challenger/src/main/java/uk/co/compendiumdev/challenge/Main.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uk.co.compendiumdev.challenge;
22

3+
import uk.co.compendiumdev.thingifier.Thingifier;
34
import uk.co.compendiumdev.thingifier.application.MainImplementation;
45
import uk.co.compendiumdev.thingifier.application.ThingifierRestServer;
56
import uk.co.compendiumdev.thingifier.application.routehandlers.ShutdownRouteHandler;
@@ -10,7 +11,8 @@ public static void main(String[] args) {
1011

1112

1213
MainImplementation app = new MainImplementation();
13-
app.registerModel("challengeapi", new ChallengeApiModel().get());
14+
Thingifier thingifier = new ChallengeApiModel().get();
15+
app.registerModel("challengeapi", thingifier);
1416

1517
// add any additional thingifier configurations here if more needed than model has defined
1618
app.setDefaultsFromArgs(args);
@@ -19,7 +21,7 @@ public static void main(String[] args) {
1921
app.setupBuiltInConfigurableRoutes();
2022

2123
// setup routes required for challenges
22-
ChallengeRouteHandler challenger = new ChallengeRouteHandler();
24+
ChallengeRouteHandler challenger = new ChallengeRouteHandler(thingifier);
2325
challenger.configureRoutes();
2426

2527
app.addAdditionalRoutes(challenger.getRoutes());
@@ -30,6 +32,8 @@ public static void main(String[] args) {
3032
app.configureThingifierWithProfile();
3133

3234
app.setupDefaultGui();
35+
challenger.setupGui(app.getGuiManagement());
36+
3337
final ThingifierRestServer restServer = app.startRestServer();
3438

3539
app.addBuiltInArgConfiguredHooks();

0 commit comments

Comments
 (0)