diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..a713c58 --- /dev/null +++ b/build.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/README.txt b/lib/README.txt new file mode 100644 index 0000000..ce80d45 --- /dev/null +++ b/lib/README.txt @@ -0,0 +1,30 @@ +Please download and install the following libraries into this folder. + +https://repo1.maven.org/maven2/org/freemarker/freemarker/2.3.23/freemarker-2.3.23.jar + +https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.7/jackson-databind-2.8.7.jar + +https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/1.5.8/mariadb-java-client-1.5.8.jar + +https://repo1.maven.org/maven2/org/nanohttpd/nanohttpd/2.3.0/nanohttpd-2.3.0.jar + +https://repo1.maven.org/maven2/org/nanohttpd/nanohttpd-nanolets/2.3.0/nanohttpd-nanolets-2.3.0.jar + +https://repo1.maven.org/maven2/net/java/dev/jna/jna/4.3.0/jna-4.3.0.jar + +https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/4.3.0/jna-platform-4.3.0.jar + +--- + +Coursework 2 involves developing the data access layer of a web-based forum application. You can do this either on a lab machine or on your own laptop. + +Please follow the instructions in the PDF file, which are repeated here: + +Download cw2-student.zip and unzip it; this creates a folder cw2-student/. +Go to cw2-student/lib/ and open README.txt; download the libraries indicated into the cw2-student/lib/ folder. +In the cw2-student/ folder, run ant compile. Start the DB VM database as usual (vagrant up, ensure you can log in with mysql) and run ant run from the cw2-student/ folder. +Browse to http://localhost:8000 and you should see "Hello world!" +You are required to submit all files in a single zip on the Blackboard course site. +If you need to re-submit a file, you must resubmit your full zip again, updated appropriately. + +For CS2 support, please use the Unit Forums (Discussion Boards) on BlackBoard, or book an appointment. diff --git a/lib/freemarker-2.3.23.jar b/lib/freemarker-2.3.23.jar new file mode 100644 index 0000000..82990f6 Binary files /dev/null and b/lib/freemarker-2.3.23.jar differ diff --git a/lib/jackson-databind-2.8.7.jar b/lib/jackson-databind-2.8.7.jar new file mode 100644 index 0000000..1d155d3 Binary files /dev/null and b/lib/jackson-databind-2.8.7.jar differ diff --git a/lib/jna-4.3.0.jar b/lib/jna-4.3.0.jar new file mode 100644 index 0000000..713354e Binary files /dev/null and b/lib/jna-4.3.0.jar differ diff --git a/lib/jna-platform-4.3.0.jar b/lib/jna-platform-4.3.0.jar new file mode 100644 index 0000000..16d3ae6 Binary files /dev/null and b/lib/jna-platform-4.3.0.jar differ diff --git a/lib/mariadb-java-client-1.5.8.jar b/lib/mariadb-java-client-1.5.8.jar new file mode 100644 index 0000000..b441a52 Binary files /dev/null and b/lib/mariadb-java-client-1.5.8.jar differ diff --git a/lib/nanohttpd-2.3.0.jar b/lib/nanohttpd-2.3.0.jar new file mode 100644 index 0000000..497912d Binary files /dev/null and b/lib/nanohttpd-2.3.0.jar differ diff --git a/lib/nanohttpd-nanolets-2.3.0.jar b/lib/nanohttpd-nanolets-2.3.0.jar new file mode 100644 index 0000000..2a4d27c Binary files /dev/null and b/lib/nanohttpd-nanolets-2.3.0.jar differ diff --git a/resources/gridlex.css b/resources/gridlex.css new file mode 100644 index 0000000..e22a8da --- /dev/null +++ b/resources/gridlex.css @@ -0,0 +1,4 @@ +/*! ========================================================================== + GRIDLEX + Just a Flexbox Grid System +========================================================================== */[class*=grid]{box-sizing:border-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;margin:0 -.5rem}.col,[class*=col-]{box-sizing:border-box;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;padding:0 .5rem 1rem}.col{-webkit-flex:1 1 0%;-ms-flex:1 1 0%;flex:1 1 0%}.grid.col,.grid[class*=col-]{margin:0;padding:0}[class*=grid-][class*=-noGutter]{margin:0}[class*=grid-][class*=-noGutter]>[class*=col]{padding:0}[class*=grid-][class*=-center]{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}[class*=grid-][class*=-right]{-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;margin-left:auto}[class*=grid-][class*=-top]{-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start}[class*=grid-][class*=-middle]{-webkit-align-items:center;-ms-flex-align:center;align-items:center}[class*=grid-][class*=-bottom]{-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end}[class*=grid-][class*=-reverse]{-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}[class*=grid-][class*=-column]{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}[class*=grid-][class*=-column]>[class*=col-]{-webkit-flex-basis:auto;-ms-flex-preferred-size:auto;flex-basis:auto}[class*=grid-][class*=-column-reverse]{-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}[class*=grid-][class*=-spaceBetween]{-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}[class*=grid-][class*=-spaceAround]{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}[class*=grid-][class*=-equalHeight]>[class*=col]{display:-webkit-flex;display:-ms-flexbox;display:flex}[class*=col-][class*=-top]{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}[class*=col-][class*=-middle]{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}[class*=col-][class*=-bottom]{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}[class*=col-][class*=-first]{-webkit-order:-1;-ms-flex-order:-1;order:-1}[class*=col-][class*=-last]{-webkit-order:1;-ms-flex-order:1;order:1}[class*=grid-1]>.col,[class*=grid-1]>[class*=col-]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=grid-2]>.col,[class*=grid-2]>[class*=col-]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=grid-3]>.col,[class*=grid-3]>[class*=col-]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=grid-4]>.col,[class*=grid-4]>[class*=col-]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=grid-5]>.col,[class*=grid-5]>[class*=col-]{-webkit-flex-basis:20%;-ms-flex-preferred-size:20%;flex-basis:20%;max-width:20%}[class*=grid-6]>.col,[class*=grid-6]>[class*=col-]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=grid-7]>.col,[class*=grid-7]>[class*=col-]{-webkit-flex-basis:14.28571429%;-ms-flex-preferred-size:14.28571429%;flex-basis:14.28571429%;max-width:14.28571429%}[class*=grid-8]>.col,[class*=grid-8]>[class*=col-]{-webkit-flex-basis:12.5%;-ms-flex-preferred-size:12.5%;flex-basis:12.5%;max-width:12.5%}[class*=grid-9]>.col,[class*=grid-9]>[class*=col-]{-webkit-flex-basis:11.11111111%;-ms-flex-preferred-size:11.11111111%;flex-basis:11.11111111%;max-width:11.11111111%}[class*=grid-10]>.col,[class*=grid-10]>[class*=col-]{-webkit-flex-basis:10%;-ms-flex-preferred-size:10%;flex-basis:10%;max-width:10%}[class*=grid-10]>[class*=col-],[class*=grid-11]>.col{-webkit-flex-basis:9.09090909%;-ms-flex-preferred-size:9.09090909%;flex-basis:9.09090909%;max-width:9.09090909%}[class*=grid-11]>[class*=col-],[class*=grid-12]>.col{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}@media screen and (max-width:80em){[class*=_lg-1]>.col,[class*=_lg-1]>[class*=col-]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=_lg-2]>.col,[class*=_lg-2]>[class*=col-]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=_lg-3]>.col,[class*=_lg-3]>[class*=col-]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=_lg-4]>.col,[class*=_lg-4]>[class*=col-]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=_lg-5]>.col,[class*=_lg-5]>[class*=col-]{-webkit-flex-basis:20%;-ms-flex-preferred-size:20%;flex-basis:20%;max-width:20%}[class*=_lg-6]>.col,[class*=_lg-6]>[class*=col-]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=_lg-7]>.col,[class*=_lg-7]>[class*=col-]{-webkit-flex-basis:14.28571429%;-ms-flex-preferred-size:14.28571429%;flex-basis:14.28571429%;max-width:14.28571429%}[class*=_lg-8]>.col,[class*=_lg-8]>[class*=col-]{-webkit-flex-basis:12.5%;-ms-flex-preferred-size:12.5%;flex-basis:12.5%;max-width:12.5%}[class*=_lg-9]>.col,[class*=_lg-9]>[class*=col-]{-webkit-flex-basis:11.11111111%;-ms-flex-preferred-size:11.11111111%;flex-basis:11.11111111%;max-width:11.11111111%}[class*=_lg-10]>.col,[class*=_lg-10]>[class*=col-]{-webkit-flex-basis:10%;-ms-flex-preferred-size:10%;flex-basis:10%;max-width:10%}[class*=_lg-10]>[class*=col-],[class*=_lg-11]>.col{-webkit-flex-basis:9.09090909%;-ms-flex-preferred-size:9.09090909%;flex-basis:9.09090909%;max-width:9.09090909%}[class*=_lg-11]>[class*=col-],[class*=_lg-12]>.col{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}}@media screen and (max-width:64em){[class*=_md-1]>.col,[class*=_md-1]>[class*=col-]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=_md-2]>.col,[class*=_md-2]>[class*=col-]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=_md-3]>.col,[class*=_md-3]>[class*=col-]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=_md-4]>.col,[class*=_md-4]>[class*=col-]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=_md-5]>.col,[class*=_md-5]>[class*=col-]{-webkit-flex-basis:20%;-ms-flex-preferred-size:20%;flex-basis:20%;max-width:20%}[class*=_md-6]>.col,[class*=_md-6]>[class*=col-]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=_md-7]>.col,[class*=_md-7]>[class*=col-]{-webkit-flex-basis:14.28571429%;-ms-flex-preferred-size:14.28571429%;flex-basis:14.28571429%;max-width:14.28571429%}[class*=_md-8]>.col,[class*=_md-8]>[class*=col-]{-webkit-flex-basis:12.5%;-ms-flex-preferred-size:12.5%;flex-basis:12.5%;max-width:12.5%}[class*=_md-9]>.col,[class*=_md-9]>[class*=col-]{-webkit-flex-basis:11.11111111%;-ms-flex-preferred-size:11.11111111%;flex-basis:11.11111111%;max-width:11.11111111%}[class*=_md-10]>.col,[class*=_md-10]>[class*=col-]{-webkit-flex-basis:10%;-ms-flex-preferred-size:10%;flex-basis:10%;max-width:10%}[class*=_md-10]>[class*=col-],[class*=_md-11]>.col{-webkit-flex-basis:9.09090909%;-ms-flex-preferred-size:9.09090909%;flex-basis:9.09090909%;max-width:9.09090909%}[class*=_md-11]>[class*=col-],[class*=_md-12]>.col{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}}@media screen and (max-width:48em){[class*=_sm-1]>.col,[class*=_sm-1]>[class*=col-]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=_sm-2]>.col,[class*=_sm-2]>[class*=col-]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=_sm-3]>.col,[class*=_sm-3]>[class*=col-]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=_sm-4]>.col,[class*=_sm-4]>[class*=col-]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=_sm-5]>.col,[class*=_sm-5]>[class*=col-]{-webkit-flex-basis:20%;-ms-flex-preferred-size:20%;flex-basis:20%;max-width:20%}[class*=_sm-6]>.col,[class*=_sm-6]>[class*=col-]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=_sm-7]>.col,[class*=_sm-7]>[class*=col-]{-webkit-flex-basis:14.28571429%;-ms-flex-preferred-size:14.28571429%;flex-basis:14.28571429%;max-width:14.28571429%}[class*=_sm-8]>.col,[class*=_sm-8]>[class*=col-]{-webkit-flex-basis:12.5%;-ms-flex-preferred-size:12.5%;flex-basis:12.5%;max-width:12.5%}[class*=_sm-9]>.col,[class*=_sm-9]>[class*=col-]{-webkit-flex-basis:11.11111111%;-ms-flex-preferred-size:11.11111111%;flex-basis:11.11111111%;max-width:11.11111111%}[class*=_sm-10]>.col,[class*=_sm-10]>[class*=col-]{-webkit-flex-basis:10%;-ms-flex-preferred-size:10%;flex-basis:10%;max-width:10%}[class*=_sm-10]>[class*=col-],[class*=_sm-11]>.col{-webkit-flex-basis:9.09090909%;-ms-flex-preferred-size:9.09090909%;flex-basis:9.09090909%;max-width:9.09090909%}[class*=_sm-11]>[class*=col-],[class*=_sm-12]>.col{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}}@media screen and (max-width:35.5em){[class*=_xs-1]>.col,[class*=_xs-1]>[class*=col-]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=_xs-2]>.col,[class*=_xs-2]>[class*=col-]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=_xs-3]>.col,[class*=_xs-3]>[class*=col-]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=_xs-4]>.col,[class*=_xs-4]>[class*=col-]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=_xs-5]>.col,[class*=_xs-5]>[class*=col-]{-webkit-flex-basis:20%;-ms-flex-preferred-size:20%;flex-basis:20%;max-width:20%}[class*=_xs-6]>.col,[class*=_xs-6]>[class*=col-]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=_xs-7]>.col,[class*=_xs-7]>[class*=col-]{-webkit-flex-basis:14.28571429%;-ms-flex-preferred-size:14.28571429%;flex-basis:14.28571429%;max-width:14.28571429%}[class*=_xs-8]>.col,[class*=_xs-8]>[class*=col-]{-webkit-flex-basis:12.5%;-ms-flex-preferred-size:12.5%;flex-basis:12.5%;max-width:12.5%}[class*=_xs-9]>.col,[class*=_xs-9]>[class*=col-]{-webkit-flex-basis:11.11111111%;-ms-flex-preferred-size:11.11111111%;flex-basis:11.11111111%;max-width:11.11111111%}[class*=_xs-10]>.col,[class*=_xs-10]>[class*=col-]{-webkit-flex-basis:10%;-ms-flex-preferred-size:10%;flex-basis:10%;max-width:10%}[class*=_xs-10]>[class*=col-],[class*=_xs-11]>.col{-webkit-flex-basis:9.09090909%;-ms-flex-preferred-size:9.09090909%;flex-basis:9.09090909%;max-width:9.09090909%}[class*=_xs-11]>[class*=col-],[class*=_xs-12]>.col{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}}[class*=grid]>[class*=col-1]{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}[class*=grid]>[class*=col-2]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=grid]>[class*=col-3]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=grid]>[class*=col-4]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=grid]>[class*=col-5]{-webkit-flex-basis:41.66666667%;-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}[class*=grid]>[class*=col-6]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=grid]>[class*=col-7]{-webkit-flex-basis:58.33333333%;-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}[class*=grid]>[class*=col-8]{-webkit-flex-basis:66.66666667%;-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}[class*=grid]>[class*=col-9]{-webkit-flex-basis:75%;-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}[class*=grid]>[class*=col-10]{-webkit-flex-basis:83.33333333%;-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}[class*=grid]>[class*=col-11]{-webkit-flex-basis:91.66666667%;-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}[class*=grid]>[class*=col-12]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=grid]>[push-left*=off-0]{margin-left:0}[class*=grid]>[push-left*=off-1]{margin-left:8.33333333%}[class*=grid]>[push-left*=off-2]{margin-left:16.66666667%}[class*=grid]>[push-left*=off-3]{margin-left:25%}[class*=grid]>[push-left*=off-4]{margin-left:33.33333333%}[class*=grid]>[push-left*=off-5]{margin-left:41.66666667%}[class*=grid]>[push-left*=off-6]{margin-left:50%}[class*=grid]>[push-left*=off-7]{margin-left:58.33333333%}[class*=grid]>[push-left*=off-8]{margin-left:66.66666667%}[class*=grid]>[push-left*=off-9]{margin-left:75%}[class*=grid]>[push-left*=off-10]{margin-left:83.33333333%}[class*=grid]>[push-left*=off-11]{margin-left:91.66666667%}[class*=grid]>[push-right*=off-0]{margin-right:0}[class*=grid]>[push-right*=off-1]{margin-right:8.33333333%}[class*=grid]>[push-right*=off-2]{margin-right:16.66666667%}[class*=grid]>[push-right*=off-3]{margin-right:25%}[class*=grid]>[push-right*=off-4]{margin-right:33.33333333%}[class*=grid]>[push-right*=off-5]{margin-right:41.66666667%}[class*=grid]>[push-right*=off-6]{margin-right:50%}[class*=grid]>[push-right*=off-7]{margin-right:58.33333333%}[class*=grid]>[push-right*=off-8]{margin-right:66.66666667%}[class*=grid]>[push-right*=off-9]{margin-right:75%}[class*=grid]>[push-right*=off-10]{margin-right:83.33333333%}[class*=grid]>[push-right*=off-11]{margin-right:91.66666667%}@media screen and (max-width:80em){[class*=grid]>[class*=_lg-1]{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}[class*=grid]>[class*=_lg-2]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=grid]>[class*=_lg-3]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=grid]>[class*=_lg-4]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=grid]>[class*=_lg-5]{-webkit-flex-basis:41.66666667%;-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}[class*=grid]>[class*=_lg-6]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=grid]>[class*=_lg-7]{-webkit-flex-basis:58.33333333%;-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}[class*=grid]>[class*=_lg-8]{-webkit-flex-basis:66.66666667%;-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}[class*=grid]>[class*=_lg-9]{-webkit-flex-basis:75%;-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}[class*=grid]>[class*=_lg-10]{-webkit-flex-basis:83.33333333%;-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}[class*=grid]>[class*=_lg-11]{-webkit-flex-basis:91.66666667%;-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}[class*=grid]>[class*=_lg-12]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=grid]>[push-left*=_lg-0]{margin-left:0}[class*=grid]>[push-left*=_lg-1]{margin-left:8.33333333%}[class*=grid]>[push-left*=_lg-2]{margin-left:16.66666667%}[class*=grid]>[push-left*=_lg-3]{margin-left:25%}[class*=grid]>[push-left*=_lg-4]{margin-left:33.33333333%}[class*=grid]>[push-left*=_lg-5]{margin-left:41.66666667%}[class*=grid]>[push-left*=_lg-6]{margin-left:50%}[class*=grid]>[push-left*=_lg-7]{margin-left:58.33333333%}[class*=grid]>[push-left*=_lg-8]{margin-left:66.66666667%}[class*=grid]>[push-left*=_lg-9]{margin-left:75%}[class*=grid]>[push-left*=_lg-10]{margin-left:83.33333333%}[class*=grid]>[push-left*=_lg-11]{margin-left:91.66666667%}[class*=grid]>[push-right*=_lg-0]{margin-right:0}[class*=grid]>[push-right*=_lg-1]{margin-right:8.33333333%}[class*=grid]>[push-right*=_lg-2]{margin-right:16.66666667%}[class*=grid]>[push-right*=_lg-3]{margin-right:25%}[class*=grid]>[push-right*=_lg-4]{margin-right:33.33333333%}[class*=grid]>[push-right*=_lg-5]{margin-right:41.66666667%}[class*=grid]>[push-right*=_lg-6]{margin-right:50%}[class*=grid]>[push-right*=_lg-7]{margin-right:58.33333333%}[class*=grid]>[push-right*=_lg-8]{margin-right:66.66666667%}[class*=grid]>[push-right*=_lg-9]{margin-right:75%}[class*=grid]>[push-right*=_lg-10]{margin-right:83.33333333%}[class*=grid]>[push-right*=_lg-11]{margin-right:91.66666667%}}@media screen and (max-width:64em){[class*=grid]>[class*=_md-1]{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}[class*=grid]>[class*=_md-2]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=grid]>[class*=_md-3]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=grid]>[class*=_md-4]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=grid]>[class*=_md-5]{-webkit-flex-basis:41.66666667%;-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}[class*=grid]>[class*=_md-6]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=grid]>[class*=_md-7]{-webkit-flex-basis:58.33333333%;-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}[class*=grid]>[class*=_md-8]{-webkit-flex-basis:66.66666667%;-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}[class*=grid]>[class*=_md-9]{-webkit-flex-basis:75%;-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}[class*=grid]>[class*=_md-10]{-webkit-flex-basis:83.33333333%;-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}[class*=grid]>[class*=_md-11]{-webkit-flex-basis:91.66666667%;-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}[class*=grid]>[class*=_md-12]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=grid]>[push-left*=_md-0]{margin-left:0}[class*=grid]>[push-left*=_md-1]{margin-left:8.33333333%}[class*=grid]>[push-left*=_md-2]{margin-left:16.66666667%}[class*=grid]>[push-left*=_md-3]{margin-left:25%}[class*=grid]>[push-left*=_md-4]{margin-left:33.33333333%}[class*=grid]>[push-left*=_md-5]{margin-left:41.66666667%}[class*=grid]>[push-left*=_md-6]{margin-left:50%}[class*=grid]>[push-left*=_md-7]{margin-left:58.33333333%}[class*=grid]>[push-left*=_md-8]{margin-left:66.66666667%}[class*=grid]>[push-left*=_md-9]{margin-left:75%}[class*=grid]>[push-left*=_md-10]{margin-left:83.33333333%}[class*=grid]>[push-left*=_md-11]{margin-left:91.66666667%}[class*=grid]>[push-right*=_md-0]{margin-right:0}[class*=grid]>[push-right*=_md-1]{margin-right:8.33333333%}[class*=grid]>[push-right*=_md-2]{margin-right:16.66666667%}[class*=grid]>[push-right*=_md-3]{margin-right:25%}[class*=grid]>[push-right*=_md-4]{margin-right:33.33333333%}[class*=grid]>[push-right*=_md-5]{margin-right:41.66666667%}[class*=grid]>[push-right*=_md-6]{margin-right:50%}[class*=grid]>[push-right*=_md-7]{margin-right:58.33333333%}[class*=grid]>[push-right*=_md-8]{margin-right:66.66666667%}[class*=grid]>[push-right*=_md-9]{margin-right:75%}[class*=grid]>[push-right*=_md-10]{margin-right:83.33333333%}[class*=grid]>[push-right*=_md-11]{margin-right:91.66666667%}}@media screen and (max-width:48em){[class*=grid]>[class*=_sm-1]{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}[class*=grid]>[class*=_sm-2]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=grid]>[class*=_sm-3]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=grid]>[class*=_sm-4]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=grid]>[class*=_sm-5]{-webkit-flex-basis:41.66666667%;-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}[class*=grid]>[class*=_sm-6]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=grid]>[class*=_sm-7]{-webkit-flex-basis:58.33333333%;-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}[class*=grid]>[class*=_sm-8]{-webkit-flex-basis:66.66666667%;-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}[class*=grid]>[class*=_sm-9]{-webkit-flex-basis:75%;-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}[class*=grid]>[class*=_sm-10]{-webkit-flex-basis:83.33333333%;-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}[class*=grid]>[class*=_sm-11]{-webkit-flex-basis:91.66666667%;-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}[class*=grid]>[class*=_sm-12]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=grid]>[push-left*=_sm-0]{margin-left:0}[class*=grid]>[push-left*=_sm-1]{margin-left:8.33333333%}[class*=grid]>[push-left*=_sm-2]{margin-left:16.66666667%}[class*=grid]>[push-left*=_sm-3]{margin-left:25%}[class*=grid]>[push-left*=_sm-4]{margin-left:33.33333333%}[class*=grid]>[push-left*=_sm-5]{margin-left:41.66666667%}[class*=grid]>[push-left*=_sm-6]{margin-left:50%}[class*=grid]>[push-left*=_sm-7]{margin-left:58.33333333%}[class*=grid]>[push-left*=_sm-8]{margin-left:66.66666667%}[class*=grid]>[push-left*=_sm-9]{margin-left:75%}[class*=grid]>[push-left*=_sm-10]{margin-left:83.33333333%}[class*=grid]>[push-left*=_sm-11]{margin-left:91.66666667%}[class*=grid]>[push-right*=_sm-0]{margin-right:0}[class*=grid]>[push-right*=_sm-1]{margin-right:8.33333333%}[class*=grid]>[push-right*=_sm-2]{margin-right:16.66666667%}[class*=grid]>[push-right*=_sm-3]{margin-right:25%}[class*=grid]>[push-right*=_sm-4]{margin-right:33.33333333%}[class*=grid]>[push-right*=_sm-5]{margin-right:41.66666667%}[class*=grid]>[push-right*=_sm-6]{margin-right:50%}[class*=grid]>[push-right*=_sm-7]{margin-right:58.33333333%}[class*=grid]>[push-right*=_sm-8]{margin-right:66.66666667%}[class*=grid]>[push-right*=_sm-9]{margin-right:75%}[class*=grid]>[push-right*=_sm-10]{margin-right:83.33333333%}[class*=grid]>[push-right*=_sm-11]{margin-right:91.66666667%}}@media screen and (max-width:35.5em){[class*=grid]>[class*=_xs-1]{-webkit-flex-basis:8.33333333%;-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}[class*=grid]>[class*=_xs-2]{-webkit-flex-basis:16.66666667%;-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}[class*=grid]>[class*=_xs-3]{-webkit-flex-basis:25%;-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}[class*=grid]>[class*=_xs-4]{-webkit-flex-basis:33.33333333%;-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}[class*=grid]>[class*=_xs-5]{-webkit-flex-basis:41.66666667%;-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}[class*=grid]>[class*=_xs-6]{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}[class*=grid]>[class*=_xs-7]{-webkit-flex-basis:58.33333333%;-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}[class*=grid]>[class*=_xs-8]{-webkit-flex-basis:66.66666667%;-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}[class*=grid]>[class*=_xs-9]{-webkit-flex-basis:75%;-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}[class*=grid]>[class*=_xs-10]{-webkit-flex-basis:83.33333333%;-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}[class*=grid]>[class*=_xs-11]{-webkit-flex-basis:91.66666667%;-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}[class*=grid]>[class*=_xs-12]{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}[class*=grid]>[push-left*=_xs-0]{margin-left:0}[class*=grid]>[push-left*=_xs-1]{margin-left:8.33333333%}[class*=grid]>[push-left*=_xs-2]{margin-left:16.66666667%}[class*=grid]>[push-left*=_xs-3]{margin-left:25%}[class*=grid]>[push-left*=_xs-4]{margin-left:33.33333333%}[class*=grid]>[push-left*=_xs-5]{margin-left:41.66666667%}[class*=grid]>[push-left*=_xs-6]{margin-left:50%}[class*=grid]>[push-left*=_xs-7]{margin-left:58.33333333%}[class*=grid]>[push-left*=_xs-8]{margin-left:66.66666667%}[class*=grid]>[push-left*=_xs-9]{margin-left:75%}[class*=grid]>[push-left*=_xs-10]{margin-left:83.33333333%}[class*=grid]>[push-left*=_xs-11]{margin-left:91.66666667%}[class*=grid]>[push-right*=_xs-0]{margin-right:0}[class*=grid]>[push-right*=_xs-1]{margin-right:8.33333333%}[class*=grid]>[push-right*=_xs-2]{margin-right:16.66666667%}[class*=grid]>[push-right*=_xs-3]{margin-right:25%}[class*=grid]>[push-right*=_xs-4]{margin-right:33.33333333%}[class*=grid]>[push-right*=_xs-5]{margin-right:41.66666667%}[class*=grid]>[push-right*=_xs-6]{margin-right:50%}[class*=grid]>[push-right*=_xs-7]{margin-right:58.33333333%}[class*=grid]>[push-right*=_xs-8]{margin-right:66.66666667%}[class*=grid]>[push-right*=_xs-9]{margin-right:75%}[class*=grid]>[push-right*=_xs-10]{margin-right:83.33333333%}[class*=grid]>[push-right*=_xs-11]{margin-right:91.66666667%}} diff --git a/resources/styles.css b/resources/styles.css new file mode 100644 index 0000000..7e7b4eb --- /dev/null +++ b/resources/styles.css @@ -0,0 +1,85 @@ +body { + background-color: #eee8d5; /* base2 */ + font-family: sans-serif; +} + +div.menu { + margin-bottom: 1ex; + padding: 1ex; + background-color: #859900; + color: #002b36; +} + +div.menuitem a { + color: #002b36; +} + +h1 { + color: #cb4b16; +} + +div.section { + display: block; + background-color: #fdf6e3; /* base3 */ + color: #2aa198; + padding-top: 0.5ex; + padding-bottom: 0.5ex; + margin: 1ex; +} + +div.section h2 { + padding-left: 1ex; + color: #dc322f; +} + +div.section p { + padding-left: 2ex; + padding-right: 2ex; +} + +div.section a { + color: #6c71c4; +} + +div.outer { + background-color: #073642; /* base02 */ +} + +div.subsection { + margin: 1ex; + padding: 0.25ex; + display: block; + background-color: #fdf6e3; /* base3 */ + color: #859900; + /* border: 1px solid #268bd2; */ +} + +pre { + margin: 1ex; + padding: 1ex; + color: #d33682; +} + + +div.alt { + background-color: #859900; + color: #002b36; +} + +div.alt a { + color: #002b36; +} + +span.key { + width: 12em; + color: #859900; + float: left; +} + +span.login { + float: right; +} + +span.like { + float: right; +} diff --git a/resources/templates/ForumView.ftl b/resources/templates/ForumView.ftl new file mode 100644 index 0000000..d170b4d --- /dev/null +++ b/resources/templates/ForumView.ftl @@ -0,0 +1,21 @@ +<#include "header.html"> + +

Forum: ${data.title}

+ +<#list data.topics as t> +
+

${t.title} +

+
+ + +
+<#if session??> +

Create new topic

+<#else> +

Log in to create new topics.

+ +
+ +<#include "footer.html"> + diff --git a/resources/templates/ForumsView.ftl b/resources/templates/ForumsView.ftl new file mode 100644 index 0000000..29d6f45 --- /dev/null +++ b/resources/templates/ForumsView.ftl @@ -0,0 +1,16 @@ +<#include "header.html"> + +

Forums

+ +<#list data.data as forum> +
+

${forum.title}

+
+ + +
+

Create new forum

+
+ +<#include "footer.html"> + diff --git a/resources/templates/NewForumView.ftl b/resources/templates/NewForumView.ftl new file mode 100644 index 0000000..bb1853d --- /dev/null +++ b/resources/templates/NewForumView.ftl @@ -0,0 +1,16 @@ +<#include "header.html"> + +

Create new forum

+ +
+
+ +

Forum name:

+

+ +

+
+
+ +<#include "footer.html"> + diff --git a/resources/templates/NewPersonView.ftl b/resources/templates/NewPersonView.ftl new file mode 100644 index 0000000..16e1a9a --- /dev/null +++ b/resources/templates/NewPersonView.ftl @@ -0,0 +1,22 @@ +<#include "header.html"> + +

Create new person

+ +
+
+ +

Name: +

+ +

Username: +

+ +

Student id: +

+ +

 

+
+
+ +<#include "footer.html"> + diff --git a/resources/templates/NewPostView.ftl b/resources/templates/NewPostView.ftl new file mode 100644 index 0000000..d3d3daa --- /dev/null +++ b/resources/templates/NewPostView.ftl @@ -0,0 +1,16 @@ +<#include "header.html"> + +

Create new post

+ +
+
+ +

+ + +

+
+
+ +<#include "footer.html"> + diff --git a/resources/templates/NewTopic.ftl b/resources/templates/NewTopic.ftl new file mode 100644 index 0000000..621ede4 --- /dev/null +++ b/resources/templates/NewTopic.ftl @@ -0,0 +1,18 @@ +<#include "header.html"> + +

Create new topic

+ +
+
+

Title:

+

+

Contents:

+

+ + +

+
+
+ +<#include "footer.html"> + diff --git a/resources/templates/PeopleView.ftl b/resources/templates/PeopleView.ftl new file mode 100644 index 0000000..31a113d --- /dev/null +++ b/resources/templates/PeopleView.ftl @@ -0,0 +1,18 @@ +<#include "header.html"> + +

List of people

+ +
+<#list data.data as p> +

${p.value} [${p.key}]

+

(log in as ${p.value})

+ +

(log out)

+
+ +
+

add person

+
+ +<#include "footer.html"> + diff --git a/resources/templates/PersonView.ftl b/resources/templates/PersonView.ftl new file mode 100644 index 0000000..ba37dac --- /dev/null +++ b/resources/templates/PersonView.ftl @@ -0,0 +1,28 @@ +<#include "header.html"> + +

Person: ${data.name}

+ +
+

Information

+
+
+
Name
+
${data.name}
+
+
+
+
+
Username
+
${data.username}
+
+
+
+
+
Student id
+
${data.studentId}
+
+
+ +
+ +<#include "footer.html"> diff --git a/resources/templates/Success.ftl b/resources/templates/Success.ftl new file mode 100644 index 0000000..c14b1aa --- /dev/null +++ b/resources/templates/Success.ftl @@ -0,0 +1,15 @@ +<#include "header.html"> + +

Success

+ +
+

${data.value}

+
+ +
+Back to main page +
+ + +<#include "footer.html"> + diff --git a/resources/templates/TopicView.ftl b/resources/templates/TopicView.ftl new file mode 100644 index 0000000..69479e7 --- /dev/null +++ b/resources/templates/TopicView.ftl @@ -0,0 +1,28 @@ +<#include "header.html"> + +

Topic: ${data.title?html}

+ +<#list data.posts as p> +
+

Post #${p.postNumber} by ${p.author} at ${p.postedAt} +<#if session??> + +

+
+${p.text?html}
+
+
+ + +
+

+<#if session??> +Reply +<#else> +Log in to reply. + +

+
+ +<#include "footer.html"> + diff --git a/resources/templates/footer.html b/resources/templates/footer.html new file mode 100644 index 0000000..c2bef8b --- /dev/null +++ b/resources/templates/footer.html @@ -0,0 +1,3 @@ + + + diff --git a/resources/templates/header.html b/resources/templates/header.html new file mode 100644 index 0000000..0c4ef0e --- /dev/null +++ b/resources/templates/header.html @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/uk/ac/bris/cs/databases/api/APIProvider.java b/src/uk/ac/bris/cs/databases/api/APIProvider.java new file mode 100644 index 0000000..4d58cdb --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/APIProvider.java @@ -0,0 +1,158 @@ +package uk.ac.bris.cs.databases.api; + +import java.util.List; +import java.util.Map; + +/** + * This is the main interface that you have to implement. + * + * Methods have a difficulty from one to three stars that indicates + * very roughly how challenging they are to implement. + * + * @author csxdb + */ +public interface APIProvider { + + /* + * 1 "Person" methods + */ + + /** + * Get a list of all users in the system as a map username -> name. + * @return A map with one entry per user of the form username -> name + * (note that usernames are unique). + * + * Difficulty: * + * Used by: /people (PeopleHandler) + * + * The implementation for this is provided. + * + */ + public Result> getUsers(); + + /** + * Get a PersonView for the person with the given username. + * @param username - the username to search for, cannot be empty. + * @return If a person with the given username exists, a fully populated + * PersonView. Otherwise, failure (or fatal on a database error). + * + * Difficulty: * + * Used by: /person/:id (PersonHandler) + */ + public Result getPersonView(String username); + + /** + * Create a new person. + * @param name - the person's name, cannot be empty. + * @param username - the person's username, cannot be empty. + * @param studentId - the person's student id. May be either NULL if the + * person is not a student or a non-empty string if they are; can not be + * an empty string. + * @return Success if no person with this username existed yet and a new + * one was created, failure if a person with this username already exists, + * fatal if something else went wrong. + * + * Difficulty: ** + * Used by: /newperson => /createperson (CreatePersonHandler) + * + * The implementation for this is provided. + * + */ + public Result addNewPerson(String name, String username, String studentId); + + /* + * 2 Forums only (no topics needed yet). + * Create some sample data in your database manually to test getForums. + * Then you can implement createForum and check that the two work together. + */ + + /** + * Get the "main page" containing a list of forums ordered alphabetically + * by title. + * @return the list of all forums; an empty list if there are no forums. + * + * Difficulty: * + * Used by: /forums (ForumsHandler) + */ + public Result> getForums(); + + /** + * Create a new forum. + * @param title - the title of the forum. Must not be null or empty and + * no forum with this name must exist yet. + * @return success if the forum was created, failure if the title was + * null, empty or such a forum already existed; fatal on other errors. + * + * Difficulty: ** + * Used by: /newforum => /createforum (CreateForumHandler) + */ + public Result createForum(String title); + + /* + * 3 Forums, topics, posts. + */ + + /** + * Get the detailed view of a single forum. + * @param id - the id of the forum to get. + * @return A view of this forum if it exists, otherwise failure. + * + * Difficulty: ** + * Used by: /forum/:id (ForumHandler) + */ + public Result getForum(int id); + + /** + * Get a view of a topic. + * @param topicId - the topic to get. + * @return The topic view if one exists with the given id, + * otherwise failure or fatal on database errors. + * + * Difficulty: ** + * Used by: /topic/:id (TopicHandler) + */ + public Result getTopic(int topicId); + + /** + * Create a post in an existing topic. + * @param topicId - the id of the topic to post in. Must refer to + * an existing topic. + * @param username - the name under which to post; user must exist. + * @param text - the content of the post, cannot be empty. + * @return success if the post was made, failure if any of the preconditions + * were not met and fatal if something else went wrong. + * + * Difficulty: ** to *** depending on schema + * Used by: /newpost/:id => /createpost (CreatePostHandler) + */ + public Result createPost(int topicId, String username, String text); + + /** + * Create a new topic in a forum. + * @param forumId - the id of the forum in which to create the topic. This + * forum must exist. + * @param username - the username under which to make this post. Must refer + * to an existing username. + * @param title - the title of this topic. Cannot be empty. + * @param text - the text of the initial post. Cannot be empty. + * @return failure if any of the preconditions are not met (forum does not + * exist, user does not exist, title or text empty); + * success if the post was created and fatal if something else went wrong. + * + * Difficulty: *** + * Used by: /newtopic/:id => /createtopic (CreateTopicHandler) + */ + public Result createTopic(int forumId, String username, String title, String text); + + /** + * Count the number of posts in a topic (without fetching them all). + * @param topicId - the topic to look at. + * @return The number of posts in this topic if it exists, otherwise a + * failure. + * + * Difficulty: * + * Not used in web interface. + */ + public Result countPostsInTopic(int topicId); + +} diff --git a/src/uk/ac/bris/cs/databases/api/ForumSummaryView.java b/src/uk/ac/bris/cs/databases/api/ForumSummaryView.java new file mode 100644 index 0000000..9ee81be --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/ForumSummaryView.java @@ -0,0 +1,37 @@ +package uk.ac.bris.cs.databases.api; + +import uk.ac.bris.cs.databases.util.Params; + +/** + * Simplified summary of a single forum that does not display topics. + * @author csxdb + */ +public class ForumSummaryView { + + /* The title of this forum. */ + private final String title; + + /* The id of this forum. */ + private final int id; + + public ForumSummaryView(int id, String title) { + + Params.cannotBeEmpty(title); + this.id = id; + this.title = title; + } + + /** + * @return the title + */ + public String getTitle() { + return title; + } + + /** + * @return the id + */ + public int getId() { + return id; + } +} diff --git a/src/uk/ac/bris/cs/databases/api/ForumView.java b/src/uk/ac/bris/cs/databases/api/ForumView.java new file mode 100644 index 0000000..8410800 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/ForumView.java @@ -0,0 +1,49 @@ +package uk.ac.bris.cs.databases.api; + +import java.util.List; + +/** + * Detail view of a single forum. + * @author csxdb + */ +public class ForumView { + + /* The id of this forum. */ + private final int id; + + /* The title of this forum. */ + private final String title; + + /* The topics in this forum, ordered by title. */ + private final List topics; + + public ForumView(int id, + String title, + List topics) { + this.id = id; + this.title = title; + this.topics = topics; + } + + /** + * @return the title + */ + public String getTitle() { + return title; + } + + /** + * @return the topics + */ + public List getTopics() { + return topics; + } + + /** + * @return the id + */ + public int getId() { + return id; + } + +} diff --git a/src/uk/ac/bris/cs/databases/api/PersonView.java b/src/uk/ac/bris/cs/databases/api/PersonView.java new file mode 100644 index 0000000..3a2062b --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/PersonView.java @@ -0,0 +1,52 @@ +package uk.ac.bris.cs.databases.api; + +import uk.ac.bris.cs.databases.util.Params; + +/** + * @author csxdb + */ +public class PersonView { + + /* The name of the person. */ + private final String name; + + /* The username of the person. */ + private final String username; + + /* The studentId of the person or the empty string if the person does not + * have a student Id. + */ + private final String studentId; + + public PersonView(String name, String username, String studentId) { + + Params.cannotBeEmpty(name); + Params.cannotBeEmpty(username); + Params.cannotBeNull(studentId); + + this.name = name; + this.username = username; + this.studentId = studentId; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * @return the studentId + */ + public String getStudentId() { + return studentId; + } +} diff --git a/src/uk/ac/bris/cs/databases/api/Result.java b/src/uk/ac/bris/cs/databases/api/Result.java new file mode 100644 index 0000000..1837581 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/Result.java @@ -0,0 +1,79 @@ +package uk.ac.bris.cs.databases.api; + +import uk.ac.bris.cs.databases.util.Params; + +/** + * The result of an operation that may or may not return a value, but may return + * an error message in case of failure. + * Result.success(v) - the operation completed successfully with value v. + * Result.failure(m) - the operation failed in a way that is to be expected + * (such as trying to create a new person with an username that already exists) + * i.e. the end user made a mistake. + * Result.fatal(m) - a problem that is not the user's fault, such as a broken + * database connection. + * @param The result type if the operation was successful. + * @author csxdb + */ +public class Result { + + private enum Outcome { SUCCESS, FAILURE, FATAL }; + + private final T value; + private final Outcome success; + private final String message; + + private Result(Outcome success, String message, T value) { + this.success = success; + this.message = message; + this.value = value; + } + + public static Result success() { + return new Result(Outcome.SUCCESS, null, null); + } + + public static Result success(U value) { + return new Result(Outcome.SUCCESS, null, value); + } + + public static Result failure(String message) { + Params.cannotBeNull(message); + return new Result(Outcome.FAILURE, message, null); + } + + public static Result fatal(String message) { + Params.cannotBeNull(message); + return new Result(Outcome.FATAL, message, null); + } + + public boolean isSuccess() { + return this.success == Outcome.SUCCESS; + } + + public T getValue() { + if (this.success != Outcome.SUCCESS) { + throw new RuntimeException(); + } + return value; + } + + public boolean isFatal() { + switch (this.success) { + case SUCCESS: + throw new RuntimeException(); + case FAILURE: + return false; + case FATAL: + return true; + } + throw new Error(); + } + + public String getMessage() { + if (this.success == Outcome.SUCCESS) { + throw new RuntimeException(); + } else { + return this.message; + } + } +} diff --git a/src/uk/ac/bris/cs/databases/api/SimplePostView.java b/src/uk/ac/bris/cs/databases/api/SimplePostView.java new file mode 100644 index 0000000..84e494f --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/SimplePostView.java @@ -0,0 +1,61 @@ +package uk.ac.bris.cs.databases.api; + +import uk.ac.bris.cs.databases.util.Params; + +/** + * View of a single post. + * @author csxdb + */ +public class SimplePostView { + + /* The number of the post in the topic - the first post of each topic is + * always number 1. + */ + private final int postNumber; + + /* The name of the post author. */ + private final String author; + + /* The contents of this post. */ + private final String text; + + /* The date/time this post was made. */ + private final String postedAt; + + public SimplePostView(int postNumber, String author, + String text, String postedAt) { + + Params.cannotBeEmpty(author); + Params.cannotBeEmpty(text); + + this.author = author; + this.postNumber = postNumber; + this.text = text; + this.postedAt = postedAt; + } + + /** + * @return the postNumber + */ + public int getPostNumber() { + return postNumber; + } + + /** + * @return the text + */ + public String getText() { + return text; + } + + /** + * @return the postedAt + */ + public String getPostedAt() { + return postedAt; + } + + public String getAuthor() { + return author; + } +} diff --git a/src/uk/ac/bris/cs/databases/api/SimpleTopicSummaryView.java b/src/uk/ac/bris/cs/databases/api/SimpleTopicSummaryView.java new file mode 100644 index 0000000..f9875dd --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/SimpleTopicSummaryView.java @@ -0,0 +1,50 @@ +package uk.ac.bris.cs.databases.api; + +import uk.ac.bris.cs.databases.util.Params; + +/** + * Simplified summary view of a topic. Just displays the title and ids. + * @author csxdb + */ +public class SimpleTopicSummaryView { + + /* The id of this topic. */ + private final int topicId; + + /* The id of the forum that contains this topic. */ + private final int forumId; + + /* The title of this topic. */ + private final String title; + + public SimpleTopicSummaryView(int topicId, int forumId, String title) { + + Params.cannotBeEmpty(title); + + this.topicId = topicId; + this.forumId = forumId; + this.title = title; + } + + /** + * @return the topicId + */ + public int getTopicId() { + return topicId; + } + + /** + * @return the forumId + */ + public int getForumId() { + return forumId; + } + + /** + * @return the title + */ + public String getTitle() { + return title; + } + +} diff --git a/src/uk/ac/bris/cs/databases/api/TopicView.java b/src/uk/ac/bris/cs/databases/api/TopicView.java new file mode 100644 index 0000000..21ff935 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/api/TopicView.java @@ -0,0 +1,50 @@ +package uk.ac.bris.cs.databases.api; + +import java.util.List; +import uk.ac.bris.cs.databases.util.Params; + +/** + * Detailed view of a single topic (i.e. the posts). + * @author csxdb + */ +public class TopicView { + + /* The id of this topic. */ + private final int topicId; + + /* The title of this topic. */ + private final String title; + + /* The posts in this topic, in the order that they were created. */ + private final List posts; + + public TopicView(int topicId, String title, + List posts) { + + Params.cannotBeEmpty(title); + Params.cannotBeEmpty(posts); + + this.topicId = topicId; + this.title = title; + this.posts = posts; + } + + public List getPosts() { + return posts; + } + + /** + * @return the topicId + */ + public int getTopicId() { + return topicId; + } + + + /** + * @return the title + */ + public String getTitle() { + return title; + } +} diff --git a/src/uk/ac/bris/cs/databases/cwk2/API.java b/src/uk/ac/bris/cs/databases/cwk2/API.java new file mode 100644 index 0000000..8df3e71 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/cwk2/API.java @@ -0,0 +1,159 @@ +package uk.ac.bris.cs.databases.cwk2; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.ForumSummaryView; +import uk.ac.bris.cs.databases.api.ForumView; +import uk.ac.bris.cs.databases.api.Result; +import uk.ac.bris.cs.databases.api.PersonView; +import uk.ac.bris.cs.databases.api.SimplePostView; +import uk.ac.bris.cs.databases.api.SimpleTopicSummaryView; +import uk.ac.bris.cs.databases.api.TopicView; + +/** + * + * @author csxdb + */ +public class API implements APIProvider { + + private final Connection c; + + public API(Connection c) { + this.c = c; + } + + /* predefined methods */ + + @Override + public Result> getUsers() { + try (Statement s = c.createStatement()) { + ResultSet r = s.executeQuery("SELECT name, username FROM Person"); + + Map data = new HashMap<>(); + while (r.next()) { + data.put(r.getString("username"), r.getString("name")); + } + + return Result.success(data); + } catch (SQLException ex) { + return Result.fatal("database error - " + ex.getMessage()); + } + } + + @Override + public Result addNewPerson(String name, String username, String studentId) { + if (studentId != null && studentId.equals("")) { + return Result.failure("StudentId can be null, but cannot be the empty string."); + } + if (name == null || name.equals("")) { + return Result.failure("Name cannot be empty."); + } + if (username == null || username.equals("")) { + return Result.failure("Username cannot be empty."); + } + + try (PreparedStatement p = c.prepareStatement( + "SELECT count(1) AS c FROM Person WHERE username = ?" + )) { + p.setString(1, username); + ResultSet r = p.executeQuery(); + + if (r.next() && r.getInt("c") > 0) { + return Result.failure("A user called " + username + " already exists."); + } + } catch (SQLException e) { + return Result.fatal(e.getMessage()); + } + + try (PreparedStatement p = c.prepareStatement( + "INSERT INTO Person (name, username, stuId) VALUES (?, ?, ?)" + )) { + p.setString(1, name); + p.setString(2, username); + p.setString(3, studentId); + p.executeUpdate(); + + c.commit(); + } catch (SQLException e) { + try { + c.rollback(); + } catch (SQLException f) { + return Result.fatal("SQL error on rollback - [" + f + + "] from handling exception " + e); + } + return Result.fatal(e.getMessage()); + } + + return Result.success(); + } + + /* level 1 */ + @Override + public Result getPersonView(String username) { + throw new UnsupportedOperationException("Not supported yet. 7"); + } + + @Override + public Result> getForums() { + + try (Statement s = c.createStatement()) { + ResultSet r = s.executeQuery("SELECT id, title FROM Forum"); + + List data = new ArrayList(); + + while (r.next()) { + data.add(new ForumSummaryView(r.getInt("id"), r.getString("title"))); + } + + return Result.success(data); + } catch (SQLException ex) { + return Result.fatal("database error - " + ex.getMessage()); + } + } + + @Override + public Result countPostsInTopic(int topicId) { + throw new UnsupportedOperationException("Not supported yet. 1"); + } + + @Override + public Result getTopic(int topicId) { + throw new UnsupportedOperationException("Not supported yet. 2"); + } + + /* level 2 */ + + @Override + public Result createForum(String title) { + throw new UnsupportedOperationException("Not supported yet. 3"); + } + + @Override + public Result getForum(int id) { + throw new UnsupportedOperationException("Not supported yet. 4"); + } + + @Override + public Result createPost(int topicId, String username, String text) { + throw new UnsupportedOperationException("Not supported yet. 5"); + } + + + /* level 3 */ + + @Override + public Result createTopic(int forumId, String username, String title, String text) { + throw new UnsupportedOperationException("Not supported yet. 6"); + } + +} diff --git a/src/uk/ac/bris/cs/databases/cwk2/Person.sql b/src/uk/ac/bris/cs/databases/cwk2/Person.sql new file mode 100644 index 0000000..7432b96 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/cwk2/Person.sql @@ -0,0 +1,11 @@ +CREATE TABLE Person ( +id INTEGER PRIMARY KEY AUTO_INCREMENT, +name VARCHAR(100) NOT NULL, +username VARCHAR(10) NOT NULL UNIQUE, +stuId VARCHAR(10) NULL +); + +CREATE TABLE Forum ( +id INTEGER PRIMARY KEY AUTO_INCREMENT, +name VARCHAR(100) NOT NULL +); diff --git a/src/uk/ac/bris/cs/databases/util/ParameterCannotBeEmptyException.java b/src/uk/ac/bris/cs/databases/util/ParameterCannotBeEmptyException.java new file mode 100644 index 0000000..81d8c6b --- /dev/null +++ b/src/uk/ac/bris/cs/databases/util/ParameterCannotBeEmptyException.java @@ -0,0 +1,9 @@ +package uk.ac.bris.cs.databases.util; + +/** + * + * @author csxdb + */ +public class ParameterCannotBeEmptyException extends ParameterException { + +} diff --git a/src/uk/ac/bris/cs/databases/util/ParameterCannotBeNullException.java b/src/uk/ac/bris/cs/databases/util/ParameterCannotBeNullException.java new file mode 100644 index 0000000..d7a549a --- /dev/null +++ b/src/uk/ac/bris/cs/databases/util/ParameterCannotBeNullException.java @@ -0,0 +1,9 @@ +package uk.ac.bris.cs.databases.util; + +/** + * + * @author csxdb + */ +public class ParameterCannotBeNullException extends ParameterException { + +} diff --git a/src/uk/ac/bris/cs/databases/util/ParameterException.java b/src/uk/ac/bris/cs/databases/util/ParameterException.java new file mode 100644 index 0000000..fc85a47 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/util/ParameterException.java @@ -0,0 +1,9 @@ +package uk.ac.bris.cs.databases.util; + +/** + * Base class for all exceptions to catch invalid parameters. + * @author csxdb + */ +public class ParameterException extends RuntimeException { + +} diff --git a/src/uk/ac/bris/cs/databases/util/Params.java b/src/uk/ac/bris/cs/databases/util/Params.java new file mode 100644 index 0000000..6aa7119 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/util/Params.java @@ -0,0 +1,30 @@ +package uk.ac.bris.cs.databases.util; + +import java.util.Collection; + +/** + * + * @author csxdb + */ +public class Params { + + public static void cannotBeNull(Object o) { + if (o == null) { + throw new ParameterCannotBeNullException(); + } + } + + public static void cannotBeEmpty(String s) { + Params.cannotBeNull(s); + if (s.equals("")) { + throw new ParameterCannotBeEmptyException(); + } + } + + public static void cannotBeEmpty(Collection c) { + Params.cannotBeNull(c); + if (c.isEmpty()) { + throw new ParameterCannotBeEmptyException(); + } + } +} diff --git a/src/uk/ac/bris/cs/databases/web/.idea/misc.xml b/src/uk/ac/bris/cs/databases/web/.idea/misc.xml new file mode 100644 index 0000000..536199e --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/uk/ac/bris/cs/databases/web/.idea/modules.xml b/src/uk/ac/bris/cs/databases/web/.idea/modules.xml new file mode 100644 index 0000000..f589ca3 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/uk/ac/bris/cs/databases/web/.idea/web.iml b/src/uk/ac/bris/cs/databases/web/.idea/web.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/.idea/web.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/uk/ac/bris/cs/databases/web/.idea/workspace.xml b/src/uk/ac/bris/cs/databases/web/.idea/workspace.xml new file mode 100644 index 0000000..31bf47d --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/.idea/workspace.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + 1588343554394 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/uk/ac/bris/cs/databases/web/AbstractHandler.java b/src/uk/ac/bris/cs/databases/web/AbstractHandler.java new file mode 100644 index 0000000..143c8e6 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/AbstractHandler.java @@ -0,0 +1,185 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.router.RouterNanoHTTPD; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import uk.ac.bris.cs.databases.api.Result; + +/** + * Code shared across web handlers. + * @author csxdb + */ +public abstract class AbstractHandler extends RouterNanoHTTPD.DefaultHandler { + + public class ValueHolder { + private final String value; + + public ValueHolder(String value) { + this.value = value; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + } + + public abstract View render(RouterNanoHTTPD.UriResource uriResource, + Map params, + NanoHTTPD.IHTTPSession session); + + @Override public String getMimeType() { + return "text/html"; + } + + @Override public NanoHTTPD.Response.IStatus getStatus() { + throw new RuntimeException("Should not happen."); + // return NanoHTTPD.Response.Status.OK; + } + + @Override + public String getText() { + throw new RuntimeException("Should not happen - using get/post"); + } + + + + class Status implements NanoHTTPD.Response.IStatus { + + private final int code; + + public Status(int code) { + this.code = code; + } + + @Override public String getDescription() { + switch (code) { + case 200: return "OK"; + case 404: return "Not found"; + case 500: return "Internal error"; + default: return "OTHER"; // naughty + } + } + + @Override public int getRequestStatus() { + return code; + } + + } + + /** Implement this to work with cookies. */ + void handleCookies(NanoHTTPD.IHTTPSession session) {} + + private NanoHTTPD.Response handle(RouterNanoHTTPD.UriResource uriResource, + Map urlParams, + NanoHTTPD.IHTTPSession session) { + View v = render(uriResource, urlParams, session); + handleCookies(session); + NanoHTTPD.Response r = NanoHTTPD.newFixedLengthResponse( + new Status(v.getCode()), + getMimeType(), + v.getContents()); + return r; + } + + @Override + public NanoHTTPD.Response get(RouterNanoHTTPD.UriResource uriResource, + Map urlParams, + NanoHTTPD.IHTTPSession session) { + + return handle(uriResource, urlParams, session); + } + + @Override + public NanoHTTPD.Response post(RouterNanoHTTPD.UriResource uriResource, + Map urlParams, + NanoHTTPD.IHTTPSession session) { + + return handle(uriResource, urlParams, session); + } + + Map parseQuery(String URIParams) { + if (URIParams == null) { + return new HashMap<>(); + } + + // stackoverflow 11640025 + Map result = new HashMap<>(); + for (String param : URIParams.split("&")) { + String pair[] = param.split("="); + if (pair.length>1) { + result.put(pair[0], pair[1]); + }else{ + result.put(pair[0], ""); + } + } + return result; + } + + View renderView(String template, Object data, Object state) { + Map viewdata = new HashMap<>(); + viewdata.put("data", data); + viewdata.put("session", state); + + Configuration c = ApplicationContext.getInstance().getTemplateConfiguration(); + + Template t; + try { + t = c.getTemplate(template); + } catch (Exception e) { + return new View(500, "Template error - " + e.getMessage()); + } + + StringWriter w = new StringWriter(); + try { + t.process(viewdata, w); + } catch (TemplateException | IOException e) { + return new View(500, "Rendering error - " + e.getMessage()); + } + + return new View(200, w.toString()); + } + + public static class ListWrapper { + private final List l; + + public static Result> wrap(Result> r) { + if (r.isSuccess()) { + return Result.success(new ListWrapper(r.getValue())); + } else { + // throw new RuntimeException("Trying to list-wrap an error."); + + /* + * The wonderful thing about monads is + * that monads are a wonderful thing. + */ + if (r.isFatal()) { + return Result.fatal(r.getMessage()); + } else { + return Result.failure(r.getMessage()); + } + } + } + + public ListWrapper(List l) { + this.l = l; + } + + public List getData() { return l; } + } + + ListWrapper wrap(List l) { + return new ListWrapper(l); + } + +} diff --git a/src/uk/ac/bris/cs/databases/web/AbstractPostHandler.java b/src/uk/ac/bris/cs/databases/web/AbstractPostHandler.java new file mode 100644 index 0000000..84d412e --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/AbstractPostHandler.java @@ -0,0 +1,82 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.NanoHTTPD.Method; +import fi.iki.elonen.router.RouterNanoHTTPD; +import java.io.IOException; +import java.util.Map; +import uk.ac.bris.cs.databases.api.Result; + +/** + * Base class for POST (new topic / forum / post) handlers. + * + * @author David + */ +public abstract class AbstractPostHandler extends AbstractHandler { + + public class ValueHolder { + private final String value; + + public ValueHolder(String value) { + this.value = value; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + } + + public class RenderPair { + final String template; + final Result data; + + public RenderPair(String template, Result data) { + this.template = template; + this.data = data; + } + } + + public abstract RenderPair handlePost(Map params, + NanoHTTPD.IHTTPSession session); + + @Override + public View render(RouterNanoHTTPD.UriResource uriResource, + Map params, + NanoHTTPD.IHTTPSession session) { + + Method method = session.getMethod(); + + if (!method.equals(Method.POST)) { + return new View(400, "Error - expected POST request, got " + method); + } + + Map m = session.getParms(); + try { + session.parseBody(m); + } catch (NanoHTTPD.ResponseException | IOException e) { + return new View(500, "Exception handling POST - " + e.getMessage()); + } + + System.out.println("[AbstractPostHandler] render " + uriResource.getUri()); + for (Map.Entry e : m.entrySet()) { + System.out.println("param " + e.getKey() + " => " + e.getValue()); + } + RenderPair rp = handlePost(m, session); + + NanoHTTPD.CookieHandler h = session.getCookies(); + String user = h.read("user"); + + if (rp.data.isSuccess()) { + return renderView(rp.template, rp.data.getValue(), user); + } else if (rp.data.isFatal()) { + return new View(500, "Fatal error - " + rp.data.getMessage()); + } else { + return new View(400, "Error - " + rp.data.getMessage()); + } + } + +} diff --git a/src/uk/ac/bris/cs/databases/web/ApplicationContext.java b/src/uk/ac/bris/cs/databases/web/ApplicationContext.java new file mode 100644 index 0000000..1d0ba38 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/ApplicationContext.java @@ -0,0 +1,51 @@ +package uk.ac.bris.cs.databases.web; + +import freemarker.template.Configuration; +import uk.ac.bris.cs.databases.api.APIProvider; + +/** + * + * @author csxdb + */ +public class ApplicationContext { + + public static ApplicationContext instance = new ApplicationContext(); + private ApplicationContext() {} + + private APIProvider api; + + private Configuration templateConfiguration; + + + public static ApplicationContext getInstance() { + return instance; + } + + /** + * @return the api + */ + public APIProvider getApi() { + return api; + } + + /** + * @param api the api to set + */ + public void setApi(APIProvider api) { + this.api = api; + } + + /** + * @return the templateConfiguration + */ + public Configuration getTemplateConfiguration() { + return templateConfiguration; + } + + /** + * @param templateConfiguration the templateConfiguration to set + */ + public void setTemplateConfiguration(Configuration templateConfiguration) { + this.templateConfiguration = templateConfiguration; + } +} diff --git a/src/uk/ac/bris/cs/databases/web/CreateForumHandler.java b/src/uk/ac/bris/cs/databases/web/CreateForumHandler.java new file mode 100644 index 0000000..e05b5b9 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/CreateForumHandler.java @@ -0,0 +1,36 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; + +/** + * Create a new forum. + * path: POST /createforum [title] + * + * @author csxdb + */ +public class CreateForumHandler extends AbstractPostHandler { + + @Override + public RenderPair handlePost(Map params, + NanoHTTPD.IHTTPSession session) { + + String title = params.get("title"); + if (title == null) { + return new RenderPair(null, Result.failure("Missing 'title'")); + } + + APIProvider api = ApplicationContext.getInstance().getApi(); + + Result r = api.createForum(title); + + if (r.isSuccess()) { + return new RenderPair("Success.ftl", + Result.success(new ValueHolder("Created new forum."))); + } else { + return new RenderPair(null, r); + } + } +} diff --git a/src/uk/ac/bris/cs/databases/web/CreatePersonHandler.java b/src/uk/ac/bris/cs/databases/web/CreatePersonHandler.java new file mode 100644 index 0000000..7fd024c --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/CreatePersonHandler.java @@ -0,0 +1,41 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; + +/** + * Create a new person. + * path: POST /createperson [name,username,stuid] + * + * @author csxdb + */ +public class CreatePersonHandler extends AbstractPostHandler { + + @Override + public RenderPair handlePost(Map params, + NanoHTTPD.IHTTPSession session) { + + String name = params.get("name"); + if (name == null || name.equals("")) { + return new RenderPair(null, Result.failure("Missing or empty name.")); + } + String username = params.get("username"); + if (username == null || username.equals("")) { + return new RenderPair(null, Result.failure("Missing or empty username.")); + } + String sid = params.get("stuid"); + + APIProvider api = ApplicationContext.getInstance().getApi(); + + Result r = api.addNewPerson(name, username, sid.equals("") ? null : sid); + + if (r.isSuccess()) { + return new RenderPair("Success.ftl", + Result.success(new ValueHolder("Created new person."))); + } else { + return new RenderPair(null, r); + } + } +} diff --git a/src/uk/ac/bris/cs/databases/web/CreatePostHandler.java b/src/uk/ac/bris/cs/databases/web/CreatePostHandler.java new file mode 100644 index 0000000..5b753b9 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/CreatePostHandler.java @@ -0,0 +1,46 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author csxdb + */ +public class CreatePostHandler extends AbstractPostHandler { + + @Override + public RenderPair handlePost(Map params, + NanoHTTPD.IHTTPSession session) { + + NanoHTTPD.CookieHandler h = session.getCookies(); + String name = h.read("user"); + + if (name == null || name.equals("")) { + return new RenderPair(null, Result.failure("Missing 'name'")); + } + + int topicId = Integer.parseInt(params.get("topic")); + if (topicId == 0) { + return new RenderPair(null, Result.failure("Got zero topic id.")); + } + + String text = params.get("text"); + if (text == null || text.equals("")) { + return new RenderPair(null, Result.failure("Missing 'text'")); + } + + APIProvider api = ApplicationContext.getInstance().getApi(); + Result r = api.createPost(topicId, name, text); + + if (!r.isSuccess()) { + return new RenderPair(null, Result.failure( + "Failed to create post - " + r.getMessage())); + } + + return new RenderPair("Success.ftl", + Result.success(new ValueHolder("Created a new post."))); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/CreateTopicHandler.java b/src/uk/ac/bris/cs/databases/web/CreateTopicHandler.java new file mode 100644 index 0000000..ec78cd6 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/CreateTopicHandler.java @@ -0,0 +1,51 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author csxdb + */ +public class CreateTopicHandler extends AbstractPostHandler { + + @Override + public RenderPair handlePost(Map params, + NanoHTTPD.IHTTPSession session) { + + NanoHTTPD.CookieHandler h = session.getCookies(); + String name = h.read("user"); + + if (name == null || name.equals("")) { + return new RenderPair(null, Result.failure("Missing 'name'")); + } + + int forumId = Integer.parseInt(params.get("forum")); + if (forumId == 0) { + return new RenderPair(null, Result.failure("Got zero forum id.")); + } + + String title = params.get("title"); + if (title == null || title.equals("")) { + return new RenderPair(null, Result.failure("Missing 'title'")); + } + + String text = params.get("text"); + if (text == null || text.equals("")) { + return new RenderPair(null, Result.failure("Missing 'text'")); + } + + APIProvider api = ApplicationContext.getInstance().getApi(); + Result r = api.createTopic(forumId, name, title, text); + + if (!r.isSuccess()) { + return new RenderPair(null, Result.failure( + "Failed to create topic - " + r.getMessage())); + } + + return new RenderPair("Success.ftl", + Result.success(new ValueHolder("Created a new topic."))); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/ForumHandler.java b/src/uk/ac/bris/cs/databases/web/ForumHandler.java new file mode 100644 index 0000000..6e2257b --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/ForumHandler.java @@ -0,0 +1,23 @@ +package uk.ac.bris.cs.databases.web; + +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.ForumView; +import uk.ac.bris.cs.databases.api.Result; + +/** + * Handler for the advanced view of a single forum. + * path: /forum2/$id + * + * @author csxdb + */ +public class ForumHandler extends SimpleHandler { + + @Override + RenderPair simpleRender(String p) throws RenderException { + int id = Integer.parseInt(p); + APIProvider api = ApplicationContext.getInstance().getApi(); + Result r = api.getForum(id); + return new RenderPair("ForumView.ftl", r); + } + +} diff --git a/src/uk/ac/bris/cs/databases/web/ForumsHandler.java b/src/uk/ac/bris/cs/databases/web/ForumsHandler.java new file mode 100644 index 0000000..dee466b --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/ForumsHandler.java @@ -0,0 +1,24 @@ +package uk.ac.bris.cs.databases.web; + +import java.util.List; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; +import uk.ac.bris.cs.databases.api.ForumSummaryView; + +/** +* The simplified forums handler (no topic info at all). + * path: /forums0 + * + * @author csxdb + */ +public class ForumsHandler extends SimpleHandler { + + @Override + RenderPair simpleRender(String p) throws RenderException { + APIProvider api = ApplicationContext.getInstance().getApi(); + Result> r = api.getForums(); + return new RenderPair("ForumsView.ftl", ListWrapper.wrap(r)); + } + + @Override boolean needsParameter() { return false; } +} diff --git a/src/uk/ac/bris/cs/databases/web/LoginHandler.java b/src/uk/ac/bris/cs/databases/web/LoginHandler.java new file mode 100644 index 0000000..5737540 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/LoginHandler.java @@ -0,0 +1,69 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.router.RouterNanoHTTPD; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author David + */ +public class LoginHandler extends AbstractHandler { + + String username = ""; + + @Override + public View render(RouterNanoHTTPD.UriResource uriResource, + Map params, + NanoHTTPD.IHTTPSession session) { + + System.out.println("[LoginHandler] render " + session.getUri()); + + String id = params.get("id"); + String template; + Result data; + + if (id == null || id.equals("")) { + username = ""; + template = "Success.ftl"; + data = Result.success(new ValueHolder("Logged out.")); + } else { + APIProvider api = ApplicationContext.getInstance().getApi(); + Result> r = api.getUsers(); + if (!r.isSuccess()) { + template = null; + data = Result.fatal("API call failed."); + } else { + Map users = r.getValue(); + String name = users.get(id); + if (name == null) { + template = null; + data = Result.failure("No such user"); + } else { + username = id; + template = "Success.ftl"; + data = Result.success(new ValueHolder("Logged in as " + name)); + } + } + } + + NanoHTTPD.CookieHandler h = session.getCookies(); + if (username.equals("")) { + h.delete("user"); + } else { + h.set("user", username + ";Path=/", 1); + } + + if (data.isSuccess()) { + System.out.println("[SimpleHandler] rendering " + template); + return renderView(template, data.getValue(), + username.equals("") ? null : username); + } else if (data.isFatal()) { + return new View(500, "Fatal error - " + data.getMessage()); + } else { + return new View(400, "Error - " + data.getMessage()); + } + } +} diff --git a/src/uk/ac/bris/cs/databases/web/NewForumHandler.java b/src/uk/ac/bris/cs/databases/web/NewForumHandler.java new file mode 100644 index 0000000..882a880 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/NewForumHandler.java @@ -0,0 +1,18 @@ +package uk.ac.bris.cs.databases.web; + +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author csxdb + */ +public class NewForumHandler extends SimpleHandler { + + @Override + RenderPair simpleRender(String p) throws RenderException { + return new RenderPair("NewForumView.ftl", Result.success()); + } + + @Override boolean needsParameter() { return false; } + +} diff --git a/src/uk/ac/bris/cs/databases/web/NewPersonHandler.java b/src/uk/ac/bris/cs/databases/web/NewPersonHandler.java new file mode 100644 index 0000000..c360bb6 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/NewPersonHandler.java @@ -0,0 +1,20 @@ +package uk.ac.bris.cs.databases.web; + +import uk.ac.bris.cs.databases.api.Result; + +/** + * Create a new person. + * path: GET /newperson + * + * @author csxdb + */ +public class NewPersonHandler extends SimpleHandler { + + @Override + RenderPair simpleRender(String p) throws RenderException { + + return new RenderPair("NewPersonView.ftl", Result.success()); + } + + @Override boolean needsParameter() { return false; } +} diff --git a/src/uk/ac/bris/cs/databases/web/NewPostHandler.java b/src/uk/ac/bris/cs/databases/web/NewPostHandler.java new file mode 100644 index 0000000..5a6543c --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/NewPostHandler.java @@ -0,0 +1,19 @@ +package uk.ac.bris.cs.databases.web; + +import java.util.HashMap; +import java.util.Map; +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author csxdb + */ +public class NewPostHandler extends SimpleHandler { + + @Override + RenderPair simpleRender(String p) throws RenderException { + Map data = new HashMap<>(); + data.put("topic", p); + return new RenderPair("NewPostView.ftl", Result.success(data)); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/NewTopicHandler.java b/src/uk/ac/bris/cs/databases/web/NewTopicHandler.java new file mode 100644 index 0000000..4d3e676 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/NewTopicHandler.java @@ -0,0 +1,32 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import java.util.HashMap; +import java.util.Map; +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author csxdb + */ +public class NewTopicHandler extends RPHandler { + + @Override + RenderPair doRender(String p, + NanoHTTPD.IHTTPSession session) + throws RenderException { + + NanoHTTPD.CookieHandler h = session.getCookies(); + String username = h.read("user"); + if (username == null || username.equals("")) { + return new RenderPair(null, Result.failure( + "You must log in to create new topics. " + + "Select 'list of users' in the top bar.")); + } + + Map data = new HashMap<>(); + data.put("forum", p); + + return new RenderPair("NewTopic.ftl", Result.success(data)); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/PeopleHandler.java b/src/uk/ac/bris/cs/databases/web/PeopleHandler.java new file mode 100644 index 0000000..6703f72 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/PeopleHandler.java @@ -0,0 +1,64 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.router.RouterNanoHTTPD; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; + +/** + * + * @author csxdb + */ +public class PeopleHandler extends AbstractHandler { + + public class KV { + private final String k; + private final String v; + + public KV(String k, String v) { + this.k = k; + this.v = v; + } + + /** + * @return the k + */ + public String getKey() { + return k; + } + + /** + * @return the v + */ + public String getValue() { + return v; + } + } + + @Override + public View render(RouterNanoHTTPD.UriResource uriResource, + Map params, + NanoHTTPD.IHTTPSession session) { + APIProvider api = ApplicationContext.getInstance().getApi(); + Result> r = api.getUsers(); + + if (r.isSuccess()) { + List people = new ArrayList<>(r.getValue().size()); + for (Map.Entry entry : r.getValue().entrySet()) { + people.add(new KV(entry.getKey(), entry.getValue())); + } + + NanoHTTPD.CookieHandler h = session.getCookies(); + String user = h.read("user"); + + return renderView("PeopleView.ftl", wrap(people), user); + } else { + return new View(500, "Database error - " + r.getMessage()); + } + + } + +} diff --git a/src/uk/ac/bris/cs/databases/web/PersonHandler.java b/src/uk/ac/bris/cs/databases/web/PersonHandler.java new file mode 100644 index 0000000..e381e3d --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/PersonHandler.java @@ -0,0 +1,19 @@ +package uk.ac.bris.cs.databases.web; + +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.PersonView; +import uk.ac.bris.cs.databases.api.Result; + +/** + * PATH /person/:id + * @author csxdb + */ +public class PersonHandler extends SimpleHandler { + + @Override + RenderPair simpleRender(String p) throws RenderException { + APIProvider api = ApplicationContext.getInstance().getApi(); + Result r = api.getPersonView(p); + return new RenderPair("PersonView.ftl", r); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/RPHandler.java b/src/uk/ac/bris/cs/databases/web/RPHandler.java new file mode 100644 index 0000000..2100fbf --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/RPHandler.java @@ -0,0 +1,78 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.router.RouterNanoHTTPD; +import java.util.Map; +import uk.ac.bris.cs.databases.api.Result; + +/** + * Handler that allows the renderer access to the session. + * It also provides the RenderPair abstraction, used primarily in the + * SimpleHandler subclass. + * @author csxdb + */ +public abstract class RPHandler extends AbstractHandler { + + public class RenderException extends Exception { + int code; + + public RenderException(int code, String message) { + super(message); + this.code = code; + } + + } + + public class RenderPair { + final String template; + final Result data; + + public RenderPair(String template, Result data) { + this.template = template; + this.data = data; + } + } + + abstract RenderPair doRender(String p, + NanoHTTPD.IHTTPSession session) throws RenderException; + + // override if you don't need one. + boolean needsParameter() { return true; } + + @Override + public View render(RouterNanoHTTPD.UriResource uriResource, + Map params, + NanoHTTPD.IHTTPSession session) { + + System.out.println("[RPHandler] render " + session.getUri()); + + // Get the id or complain. + + String id = params.get("id"); + if (needsParameter()) { + if (id == null || id.equals("")) { + return new View(404, "Missing parameter."); + } + } + + try { + RenderPair rp = doRender(id, session); + + NanoHTTPD.CookieHandler h = session.getCookies(); + String user = h.read("user"); + + if (rp.data.isSuccess()) { + System.out.println("[SimpleHandler] rendering " + rp.template); + return renderView(rp.template, rp.data.getValue(), user); + } else if (rp.data.isFatal()) { + return new View(500, "Fatal error - " + rp.data.getMessage()); + } else { + return new View(400, "Error - " + rp.data.getMessage()); + } + + } catch (RenderException e) { + return new View(e.code, e.getMessage()); + } + + } +} diff --git a/src/uk/ac/bris/cs/databases/web/Server.java b/src/uk/ac/bris/cs/databases/web/Server.java new file mode 100644 index 0000000..c907e29 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/Server.java @@ -0,0 +1,99 @@ +/* + * Mini implementation forum server and UI. + */ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.router.RouterNanoHTTPD; +import fi.iki.elonen.util.ServerRunner; +import freemarker.template.Configuration; +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.cwk2.API; + +/** + * @author csxdb + */ +public class Server extends RouterNanoHTTPD { + + private static final String DATABASE = "jdbc:mariadb://localhost:3306/bb?user=student"; + + public Server() { + super(8000); + addMappings(); + } + + @Override public void addMappings() { + super.addMappings(); + addRoute("/person/:id", PersonHandler.class); + addRoute("/people", PeopleHandler.class); + addRoute("/newtopic", NewTopicHandler.class); + addRoute("/forums", ForumsHandler.class); + addRoute("/forum/:id", ForumHandler.class); + addRoute("/topic/:id", TopicHandler.class); + + addRoute("/newforum", NewForumHandler.class); + addRoute("/createforum", CreateForumHandler.class); + + addRoute("/newtopic/:id", NewTopicHandler.class); + addRoute("/createtopic", CreateTopicHandler.class); + + addRoute("/newpost/:id", NewPostHandler.class); + addRoute("/createpost", CreatePostHandler.class); + + addRoute("/newperson", NewPersonHandler.class); + addRoute("/createperson", CreatePersonHandler.class); + + addRoute("/login", LoginHandler.class); + addRoute("/login/:id", LoginHandler.class); + + addRoute("/styles.css", StyleHandler.class, "resources/styles.css"); + addRoute("/gridlex.css", StyleHandler.class, "resources/gridlex.css"); + } + + public static void main(String[] args) throws Exception { + + ApplicationContext c = ApplicationContext.getInstance(); + + // database // + + Connection conn; + try { + String cs = DATABASE; + if (args.length >= 1) { + cs = cs + "&localSocket=" + args[0]; + System.out.println("Using DB socket file: " + args[0]); + } else { + //System.out.println("Not using a DB socket file."); + } + + conn = DriverManager.getConnection(cs); + conn.setAutoCommit(false); + APIProvider api = new API(conn); + c.setApi(api); + + // AF: Info messages + System.out.println("Server accessible at: http://localhost:8000"); + System.out.println("Forums accessible at: http://localhost:8000/forums"); + } catch (SQLException e) { + System.out.println("Connection to database failed. " + + "Check that the database is running and that the socket file " + + "is correct if you are using one."); + throw new RuntimeException(e); + } + + // templating // + + Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + cfg.setDirectoryForTemplateLoading(new File("resources/templates")); + cfg.setDefaultEncoding("UTF-8"); + c.setTemplateConfiguration(cfg); + + // server // + + Server server = new Server(); + ServerRunner.run(Server.class); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/SimpleHandler.java b/src/uk/ac/bris/cs/databases/web/SimpleHandler.java new file mode 100644 index 0000000..147994c --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/SimpleHandler.java @@ -0,0 +1,19 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; + +/** + * Handler for the simple case that we're dealing with a GET request, have a + * single URI parameter of interest and want to render a view with some data + * of complain if we get junk back. + * @author csxdb + */ +public abstract class SimpleHandler extends RPHandler { + + abstract RenderPair simpleRender(String p) throws RenderException; + + @Override public RenderPair doRender(String p, + NanoHTTPD.IHTTPSession session) throws RenderException { + return simpleRender(p); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/StyleHandler.java b/src/uk/ac/bris/cs/databases/web/StyleHandler.java new file mode 100644 index 0000000..5fbcd7f --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/StyleHandler.java @@ -0,0 +1,48 @@ +package uk.ac.bris.cs.databases.web; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.router.RouterNanoHTTPD; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Map; + +/** + * + * @author David + */ +public class StyleHandler extends AbstractHandler { + + @Override + public View render(RouterNanoHTTPD.UriResource uriResource, + Map params, + NanoHTTPD.IHTTPSession session) { + + String filename = uriResource.initParameter(String.class); + + try { + File f = new File(filename); + FileReader fr = new FileReader(f); + BufferedReader br = new BufferedReader(fr); + + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + sb.append("\n"); + } + br.close(); + return new View(200, sb.toString()); + } catch (IOException e) { + return new View(500, "Error reading file - " + e.getMessage()); + } + } + + @Override + public String getMimeType() { + return "text/css"; + } + + +} diff --git a/src/uk/ac/bris/cs/databases/web/TopicHandler.java b/src/uk/ac/bris/cs/databases/web/TopicHandler.java new file mode 100644 index 0000000..a89ae46 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/TopicHandler.java @@ -0,0 +1,21 @@ +package uk.ac.bris.cs.databases.web; + +import uk.ac.bris.cs.databases.api.APIProvider; +import uk.ac.bris.cs.databases.api.Result; +import uk.ac.bris.cs.databases.api.TopicView; + +/** + * + * @author csxdb + */ +public class TopicHandler extends SimpleHandler { + + @Override + public RenderPair simpleRender(String p) { + + int id = Integer.parseInt(p); + APIProvider api = ApplicationContext.getInstance().getApi(); + Result r = api.getTopic(id); + return new RenderPair("TopicView.ftl", r); + } +} diff --git a/src/uk/ac/bris/cs/databases/web/View.java b/src/uk/ac/bris/cs/databases/web/View.java new file mode 100644 index 0000000..8401c44 --- /dev/null +++ b/src/uk/ac/bris/cs/databases/web/View.java @@ -0,0 +1,46 @@ +package uk.ac.bris.cs.databases.web; + +/** + * + * @author csxdb + */ +public class View { + private int code; + private String contents; + + public View() { + } + + public View(int code, String contents) { + this.code = code; + this.contents = contents; + } + + /** + * @return the code + */ + public int getCode() { + return code; + } + + /** + * @param code the code to set + */ + public void setCode(int code) { + this.code = code; + } + + /** + * @return the contents + */ + public String getContents() { + return contents; + } + + /** + * @param contents the contents to set + */ + public void setContents(String contents) { + this.contents = contents; + } +} diff --git a/target/uk/ac/bris/cs/databases/api/APIProvider.class b/target/uk/ac/bris/cs/databases/api/APIProvider.class new file mode 100644 index 0000000..c029d96 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/APIProvider.class differ diff --git a/target/uk/ac/bris/cs/databases/api/ForumSummaryView.class b/target/uk/ac/bris/cs/databases/api/ForumSummaryView.class new file mode 100644 index 0000000..12c5bad Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/ForumSummaryView.class differ diff --git a/target/uk/ac/bris/cs/databases/api/ForumView.class b/target/uk/ac/bris/cs/databases/api/ForumView.class new file mode 100644 index 0000000..05c9a4a Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/ForumView.class differ diff --git a/target/uk/ac/bris/cs/databases/api/PersonView.class b/target/uk/ac/bris/cs/databases/api/PersonView.class new file mode 100644 index 0000000..4897112 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/PersonView.class differ diff --git a/target/uk/ac/bris/cs/databases/api/Result$1.class b/target/uk/ac/bris/cs/databases/api/Result$1.class new file mode 100644 index 0000000..1acccd2 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/Result$1.class differ diff --git a/target/uk/ac/bris/cs/databases/api/Result$Outcome.class b/target/uk/ac/bris/cs/databases/api/Result$Outcome.class new file mode 100644 index 0000000..4ab3883 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/Result$Outcome.class differ diff --git a/target/uk/ac/bris/cs/databases/api/Result.class b/target/uk/ac/bris/cs/databases/api/Result.class new file mode 100644 index 0000000..38d52f4 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/Result.class differ diff --git a/target/uk/ac/bris/cs/databases/api/SimplePostView.class b/target/uk/ac/bris/cs/databases/api/SimplePostView.class new file mode 100644 index 0000000..718d1ef Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/SimplePostView.class differ diff --git a/target/uk/ac/bris/cs/databases/api/SimpleTopicSummaryView.class b/target/uk/ac/bris/cs/databases/api/SimpleTopicSummaryView.class new file mode 100644 index 0000000..0f92d26 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/SimpleTopicSummaryView.class differ diff --git a/target/uk/ac/bris/cs/databases/api/TopicView.class b/target/uk/ac/bris/cs/databases/api/TopicView.class new file mode 100644 index 0000000..ea77a0e Binary files /dev/null and b/target/uk/ac/bris/cs/databases/api/TopicView.class differ diff --git a/target/uk/ac/bris/cs/databases/cwk2/API.class b/target/uk/ac/bris/cs/databases/cwk2/API.class new file mode 100644 index 0000000..5fb1172 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/cwk2/API.class differ diff --git a/target/uk/ac/bris/cs/databases/util/ParameterCannotBeEmptyException.class b/target/uk/ac/bris/cs/databases/util/ParameterCannotBeEmptyException.class new file mode 100644 index 0000000..c6a8c56 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/util/ParameterCannotBeEmptyException.class differ diff --git a/target/uk/ac/bris/cs/databases/util/ParameterCannotBeNullException.class b/target/uk/ac/bris/cs/databases/util/ParameterCannotBeNullException.class new file mode 100644 index 0000000..878900f Binary files /dev/null and b/target/uk/ac/bris/cs/databases/util/ParameterCannotBeNullException.class differ diff --git a/target/uk/ac/bris/cs/databases/util/ParameterException.class b/target/uk/ac/bris/cs/databases/util/ParameterException.class new file mode 100644 index 0000000..2347042 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/util/ParameterException.class differ diff --git a/target/uk/ac/bris/cs/databases/util/Params.class b/target/uk/ac/bris/cs/databases/util/Params.class new file mode 100644 index 0000000..3c7597d Binary files /dev/null and b/target/uk/ac/bris/cs/databases/util/Params.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractHandler$ListWrapper.class b/target/uk/ac/bris/cs/databases/web/AbstractHandler$ListWrapper.class new file mode 100644 index 0000000..d62b12c Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractHandler$ListWrapper.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractHandler$Status.class b/target/uk/ac/bris/cs/databases/web/AbstractHandler$Status.class new file mode 100644 index 0000000..3143967 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractHandler$Status.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractHandler$ValueHolder.class b/target/uk/ac/bris/cs/databases/web/AbstractHandler$ValueHolder.class new file mode 100644 index 0000000..d5dcaef Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractHandler$ValueHolder.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractHandler.class b/target/uk/ac/bris/cs/databases/web/AbstractHandler.class new file mode 100644 index 0000000..277c048 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractPostHandler$RenderPair.class b/target/uk/ac/bris/cs/databases/web/AbstractPostHandler$RenderPair.class new file mode 100644 index 0000000..038e49c Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractPostHandler$RenderPair.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractPostHandler$ValueHolder.class b/target/uk/ac/bris/cs/databases/web/AbstractPostHandler$ValueHolder.class new file mode 100644 index 0000000..6ac4ea4 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractPostHandler$ValueHolder.class differ diff --git a/target/uk/ac/bris/cs/databases/web/AbstractPostHandler.class b/target/uk/ac/bris/cs/databases/web/AbstractPostHandler.class new file mode 100644 index 0000000..91b7902 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/AbstractPostHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/ApplicationContext.class b/target/uk/ac/bris/cs/databases/web/ApplicationContext.class new file mode 100644 index 0000000..51659be Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/ApplicationContext.class differ diff --git a/target/uk/ac/bris/cs/databases/web/CreateForumHandler.class b/target/uk/ac/bris/cs/databases/web/CreateForumHandler.class new file mode 100644 index 0000000..d75c6a9 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/CreateForumHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/CreatePersonHandler.class b/target/uk/ac/bris/cs/databases/web/CreatePersonHandler.class new file mode 100644 index 0000000..3a5bf86 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/CreatePersonHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/CreatePostHandler.class b/target/uk/ac/bris/cs/databases/web/CreatePostHandler.class new file mode 100644 index 0000000..ab0a538 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/CreatePostHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/CreateTopicHandler.class b/target/uk/ac/bris/cs/databases/web/CreateTopicHandler.class new file mode 100644 index 0000000..bbffb3a Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/CreateTopicHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/ForumHandler.class b/target/uk/ac/bris/cs/databases/web/ForumHandler.class new file mode 100644 index 0000000..3f903ed Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/ForumHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/ForumsHandler.class b/target/uk/ac/bris/cs/databases/web/ForumsHandler.class new file mode 100644 index 0000000..9f9b5a9 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/ForumsHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/LoginHandler.class b/target/uk/ac/bris/cs/databases/web/LoginHandler.class new file mode 100644 index 0000000..0fafd37 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/LoginHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/NewForumHandler.class b/target/uk/ac/bris/cs/databases/web/NewForumHandler.class new file mode 100644 index 0000000..5337300 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/NewForumHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/NewPersonHandler.class b/target/uk/ac/bris/cs/databases/web/NewPersonHandler.class new file mode 100644 index 0000000..b5b04ea Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/NewPersonHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/NewPostHandler.class b/target/uk/ac/bris/cs/databases/web/NewPostHandler.class new file mode 100644 index 0000000..0265eff Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/NewPostHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/NewTopicHandler.class b/target/uk/ac/bris/cs/databases/web/NewTopicHandler.class new file mode 100644 index 0000000..62eedc8 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/NewTopicHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/PeopleHandler$KV.class b/target/uk/ac/bris/cs/databases/web/PeopleHandler$KV.class new file mode 100644 index 0000000..7d5b569 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/PeopleHandler$KV.class differ diff --git a/target/uk/ac/bris/cs/databases/web/PeopleHandler.class b/target/uk/ac/bris/cs/databases/web/PeopleHandler.class new file mode 100644 index 0000000..2dd0280 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/PeopleHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/PersonHandler.class b/target/uk/ac/bris/cs/databases/web/PersonHandler.class new file mode 100644 index 0000000..062b471 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/PersonHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/RPHandler$RenderException.class b/target/uk/ac/bris/cs/databases/web/RPHandler$RenderException.class new file mode 100644 index 0000000..ac64a72 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/RPHandler$RenderException.class differ diff --git a/target/uk/ac/bris/cs/databases/web/RPHandler$RenderPair.class b/target/uk/ac/bris/cs/databases/web/RPHandler$RenderPair.class new file mode 100644 index 0000000..ff673e7 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/RPHandler$RenderPair.class differ diff --git a/target/uk/ac/bris/cs/databases/web/RPHandler.class b/target/uk/ac/bris/cs/databases/web/RPHandler.class new file mode 100644 index 0000000..11a812f Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/RPHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/Server.class b/target/uk/ac/bris/cs/databases/web/Server.class new file mode 100644 index 0000000..aec43c3 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/Server.class differ diff --git a/target/uk/ac/bris/cs/databases/web/SimpleHandler.class b/target/uk/ac/bris/cs/databases/web/SimpleHandler.class new file mode 100644 index 0000000..3eb00a5 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/SimpleHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/StyleHandler.class b/target/uk/ac/bris/cs/databases/web/StyleHandler.class new file mode 100644 index 0000000..75f5410 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/StyleHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/TopicHandler.class b/target/uk/ac/bris/cs/databases/web/TopicHandler.class new file mode 100644 index 0000000..ac977a6 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/TopicHandler.class differ diff --git a/target/uk/ac/bris/cs/databases/web/View.class b/target/uk/ac/bris/cs/databases/web/View.class new file mode 100644 index 0000000..3aedf85 Binary files /dev/null and b/target/uk/ac/bris/cs/databases/web/View.class differ