diff --git a/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java b/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java index ff59f35..f2408f4 100644 --- a/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java +++ b/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java @@ -4,6 +4,7 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.WriteConcern; +import com.mongodb.WriteConcern; import com.mongodb.bulk.BulkWriteResult; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -773,6 +774,14 @@ public class MongoDBHandler { return count > 0; } + public String getMemberPhoto(String memberId) { + Document photoDocument = memberPhotoCollection.find(eq("memberId", memberId)).first(); + if (photoDocument == null) { + return null; + } + return photoDocument.getString("base64"); + } + public void close() { mongoClient.close(); } diff --git a/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java new file mode 100644 index 0000000..c0f675f --- /dev/null +++ b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java @@ -0,0 +1,161 @@ +package org.texttechnologylab.project.gruppe_05_1.rest; + +import gnu.trove.impl.sync.TSynchronizedShortObjectMap; +import io.javalin.http.Context; +import io.javalin.openapi.*; +import org.apache.commons.collections.bag.SynchronizedSortedBag; +import org.texttechnologylab.project.gruppe_05_1.database.MongoPprUtils; +import org.texttechnologylab.project.gruppe_05_1.domain.html.HtmlSpeech; +import org.texttechnologylab.project.gruppe_05_1.domain.html.Parlamentarier; +import org.texttechnologylab.project.gruppe_05_1.domain.html.ParlamentarierDetails; +import org.texttechnologylab.project.gruppe_05_1.domain.speech.SpeechMetaData; +import org.texttechnologylab.project.gruppe_05_1.util.Logger; +import org.texttechnologylab.project.gruppe_05_1.util.PPRUtils; +import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Interfaces.Speech; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FrontEndController { + @OpenApi( + summary = "Get the homepage.", + description = "Get the homepage", + operationId = "getHomepage", + path = "/", + methods = HttpMethod.GET, + tags = {"Homepage"}, + responses = { + @OpenApiResponse(status = "200") + }) + public static void getHomepage(Context ctx) { + ctx.render("home.ftl"); + } + + @OpenApi( + summary = "Get alle Parlamentarier. Man kann nach Vor-, Nachname oder Partei filtern.", + description = "Listet alle Parlamentarier bzw. diejenige, welche den Filter entsprechen", + operationId = "getAllParlamentarier", + path = "/", + methods = HttpMethod.GET, + tags = {"Parlamentarier"}, + queryParams = { + @OpenApiParam(name = "filter", description = "Full-Text-Filter. Kann Vorname, Nachname oder Partei filtern", required = false), + }, + responses = { + @OpenApiResponse(status = "200", content = {@OpenApiContent(from = Parlamentarier[].class)}) + }) + public static void getAllParlamentarier(Context ctx) { + String filter = ctx.queryParam("filter"); + Logger.info("Filter: '" + filter + "'"); + + List parlamentarier = MongoPprUtils.getAllParlamentarier(filter); + PPRUtils.sortParlamentarierByName(parlamentarier); + Logger.info(parlamentarier.size() + " MdBs gefunden"); + + Map attributes = new HashMap<>(); + attributes.put("parlamentarier", parlamentarier); + attributes.put("filter", filter); + ctx.render("parlamentarier.ftl", attributes); + } + + /** + * Zeigt die Details eines Parlamentariers an: + * - persönliche Daten (Geburtsdatum, -ort, Vita, Religion etc.). + * - Mitgliederschaften, falls vorhanden + * - Fotos, falls vorhanden + * @param ctx JavaLin-Context + */ + + @OpenApi( + summary = "Zeigt die Details eines Parlamentariers an", + description = "Zeigt persönliche Daten, Mitgliederschaften, Fotos", + operationId = "getParlamentarierDetails", + path = "/portfolio/{id}", + methods = HttpMethod.GET, + tags = {"Parlamentarier"}, + pathParams = { + @OpenApiParam(name = "id", description = "id des Parlamentariers", required = true), + }, + responses = { + @OpenApiResponse(status = "200", content = {@OpenApiContent(from = ParlamentarierDetails.class)}) + }) + public static void getParlamentarierDetails(Context ctx) { + String id = ctx.pathParam("id"); + Logger.info("getParlamentarierDetails, ID = " + id); + + ParlamentarierDetails pd = MongoPprUtils.getParlamentarierDetailsByID(id); + + Map attributes = new HashMap<>(); + attributes.put("p", pd); + Long speechCount = MongoPprUtils.countSpeechesOfSpeaker(pd.getId()); + attributes.put("speechesCount", speechCount); + attributes.put("pic", MongoPprUtils.getMemberPhoto(pd.getId())); + if (speechCount == 0) { + attributes.put("speechesPlaceholder", null); + } else { + attributes.put("speechesPlaceholder", new ArrayList<>()); + } + + ctx.render("parlamentarierDetails.ftl", attributes); + } + + /** + * Liste alle Reden eines Parlamentariers an + * @param ctx Javalin Context + */ + @OpenApi( + summary = "Liste alle Reden eines Parlamentariers an", + description = "Liste alle Reden eines Parlamentariers an", + operationId = "listSpeeches", + path = "/reden/{id}", + methods = HttpMethod.GET, + tags = {"Rede"}, + pathParams = { + @OpenApiParam(name = "id", description = "id des Parlamentariers", required = true), + }, + responses = { + @OpenApiResponse(status = "200", content = {@OpenApiContent(from = Speech[].class)}) + }) + public static void listSpeeches(Context ctx) { + String parlamentarierId = ctx.pathParam("id"); + + ParlamentarierDetails p = MongoPprUtils.getParlamentarierDetailsByID(parlamentarierId); + List speechMetaDataList = MongoPprUtils.getSpeechesMetadataForSpeaker(parlamentarierId); + + Map attributes = new HashMap<>(); + attributes.put("p", p); + attributes.put("speechesMetaDataList", speechMetaDataList); + ctx.render("showSpeechesList.ftl", attributes); + } + + /** + * Zeige eine bestimmte Rede des Parlamentariers an + * @param ctx Javalin Context + */ + @OpenApi( + summary = "Zeige eine bestimmte Rede des Parlamentariers an", + description = "Zeige eine bestimmte Rede des Parlamentariers an", + operationId = "showSpeech", + path = "/reden/{id}/{redeID}", + methods = HttpMethod.GET, + tags = {"Rede"}, + pathParams = { + @OpenApiParam(name = "id", description = "id des Parlamentariers", required = true), + @OpenApiParam(name = "redeId", description = "id der Rede", required = true), + }, + responses = { + @OpenApiResponse(status = "200", content = {@OpenApiContent(from = Speech.class)}) + }) + public static void showSpeech(Context ctx) { + String redeId = ctx.pathParam("redeId"); + + Map attributes = new HashMap<>(); + + HtmlSpeech speech = MongoPprUtils.getSpeechByKey(redeId); + attributes.put("s", speech); + + ctx.render("speech.ftl", attributes); + } +} diff --git a/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandlerOld.java b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandlerOld.java new file mode 100644 index 0000000..84be658 --- /dev/null +++ b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandlerOld.java @@ -0,0 +1,69 @@ +package org.texttechnologylab.project.gruppe_05_1.rest; + +import freemarker.template.Configuration; +import freemarker.template.TemplateExceptionHandler; +import io.javalin.Javalin; +import io.javalin.http.staticfiles.Location; +import io.javalin.openapi.plugin.OpenApiPlugin; +import io.javalin.openapi.plugin.redoc.ReDocPlugin; +import io.javalin.rendering.template.JavalinFreemarker; +import org.texttechnologylab.project.gruppe_05_1.util.Logger; + +import java.io.File; +import java.io.IOException; + +import static org.texttechnologylab.project.gruppe_05_1.Main.JAVALIN_STATIC_FILES_DIR; +import static org.texttechnologylab.project.gruppe_05_1.Main.JAVALIN_TEMPLATE_DIR; + +public class RESTHandlerOld { + + public void startJavalin() { + + // Javalin Konfiguration (z.B. port) + JavalinConfig jlConfig = new JavalinConfig(); + int port = jlConfig.getPort(); + + // FreeMarker Konfiguration + Configuration fmConfig = new Configuration(Configuration.VERSION_2_3_33); + fmConfig.setDefaultEncoding("UTF-8"); + try { + fmConfig.setDirectoryForTemplateLoading(new File(JAVALIN_TEMPLATE_DIR)); + } catch (IOException e) { + throw new RuntimeException(e); + } + fmConfig.setLogTemplateExceptions(true); + fmConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + // Erzeuge die Javalin app + Javalin app = Javalin.create(config -> { + config.staticFiles.add(JAVALIN_STATIC_FILES_DIR, Location.EXTERNAL); // momentan nicht benutzt + + config.fileRenderer(new JavalinFreemarker(fmConfig)); + + config.registerPlugin(new OpenApiPlugin(pluginConfig -> { + // Define OpenAPI spec configuration + pluginConfig.withDefinitionConfiguration((version, definition) -> { + definition.withOpenApiInfo(info -> info.setTitle("Javalin OpenAPI Documentation")); + }); + })); + + config.registerPlugin(new ReDocPlugin()); + + }) + .start(port); + Logger.info("Javalin app started on http://localhost:" + port); + + // Routes + // ====== + + // Parlamentarier + app.get("/", ParlamentarierController::getAllParlamentarier); + app.get("/portfolio/{id}", ParlamentarierController::getParlamentarierDetails); + app.delete("/deleteParlamentarier", ParlamentarierController::deleteAllParlamentarier); + + // Reden + app.get("/reden/{id}", SpeechController::listSpeeches); // zeige Reden eines Parlamentariers an + app.get("/reden/{id}/{redeId}", SpeechController::showSpeech); // zeige eine bestimmte Rede des Parlamentariers an + + } +} diff --git a/src/main/resources/public/index.html b/src/main/resources/public/index.html deleted file mode 100644 index 7f481fd..0000000 --- a/src/main/resources/public/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - -

Heading

-

paragraph.

- - - \ No newline at end of file diff --git a/src/main/resources/static/index.css b/src/main/resources/static/index.css new file mode 100644 index 0000000..ca06fc5 --- /dev/null +++ b/src/main/resources/static/index.css @@ -0,0 +1,214 @@ +@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css'); + +:root { + --primary-color: #333; + --accent-color: #a00000; + --background-color: #F4F4F9; + --confirm-button-color: #28a745; + --confirm-button-color-hover: #218838; + --header-background-color: darkgray; +} + + +/* General Reset */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + background-color: var(--background-color) +} + +header { + background-color: var(--header-background-color); + position: fixed; + top: 0; + left: 0; + right: 0; +} + +nav { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +nav a { + color: white; + text-decoration: none; + font-size: 1.25rem; + margin: 10px; + margin-top: 0; + padding: 5px 10px 5px 10px; + border: solid 1px white; + border-radius: 5px +} + +footer { + background-color: black; + color: white; + text-align: center; + position: fixed; + bottom: 0; + left: 0; + right: 0; +} + +/* Body Styling */ +body { + font-family: 'Arial', sans-serif; + background-color: #f4f4f9; + color: var(--primary-color); + line-height: 1.6; + padding: 20px; + margin-top: 120px +} + +/* Heading Styling */ +h1, h2 { + text-align: center; + font-size: 2.5rem; + color: var(--accent-color); + margin-bottom: 30px; +} + +h2 { + font-size: 1.5rem; +} + +/* Form Styling */ +form { + display: flex; + justify-content: center; + gap: 15px; + margin-bottom: 30px; +} + +input[type="text"] { + padding: 10px; + font-size: 1rem; + border: 1px solid #ddd; + border-radius: 4px; + width: 200px; +} + +button { + padding: 10px 20px; + font-size: 1rem; + background-color: var(--accent-color); + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +button:hover { + background-color: var(--accent-color); +} + +/* Table Styling */ +table { + width: 100%; + border-collapse: collapse; + margin-top: 30px; + background-color: #fff; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +th, td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +th { + background-color: + var(--background-color); + color: var(--accent-color); + font-size: 1.2rem; +} + +td { + font-size: 1rem; +} + +td a { + text-decoration: none; + color: var(--accent-color); + font-weight: bold; + transition: color 0.3s ease; +} + +td a:hover { + color: var(--accent-color); +} + +tbody tr:hover { + background-color: var(--background-color); +} + +/* Responsive Styling */ +@media (max-width: 768px) { + table, th, td { + font-size: 0.9rem; + } + + form { + flex-direction: column; + align-items: center; + } + + input[type="text"] { + width: 100%; + max-width: 300px; + } + + button { + width: 100%; + max-width: 150px; + } +} + + +.add-member-button { + background-color: var(--confirm-button-color); /* Green color */ + color: white; + padding: 10px 20px; + text-decoration: none; + border-radius: 5px; + font-size: 16px; + display: inline-block; + transition: background-color 0.3s ease; +} + +/* Change button color on hover */ +.add-member-button:hover { + background-color: var(--confirm-button-color-hover); /* Darker green on hover */ +} + +#search-button { + width: 75px; + background-color: var(--confirm-button-color); + border-radius: 5px; + border: 1px solid lightgray; +} + +#search-button:hover { + background-color: var(--confirm-button-color-hover); +} + +.back-link { + position: fixed; + bottom: 50px; + right: 50px; + background-color: var(--accent-color); + border-radius: 5px; + padding: 5px 10px 5px 10px; + text-decoration: none; + color: white +} \ No newline at end of file