Bookmark submission: Fetch page info through back end.

CORS prevents us from fetch the target page directly.  This patch
makes the front end use the back end to fetch it instead.

Change-Id: I2d33a68d00b6ce1bb7a7b8dfcb7687f0bd1fdebd
diff --git a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java
index 73396d7..a535d0d 100644
--- a/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java
+++ b/src/main/java/eu/mulk/mulkcms2/benki/bookmarks/BookmarkResource.java
@@ -1,5 +1,6 @@
 package eu.mulk.mulkcms2.benki.bookmarks;
 
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import static javax.ws.rs.core.MediaType.TEXT_HTML;
 
 import eu.mulk.mulkcms2.benki.accesscontrol.Role;
@@ -11,6 +12,7 @@
 import io.quarkus.qute.api.ResourcePath;
 import io.quarkus.security.Authenticated;
 import io.quarkus.security.identity.SecurityIdentity;
+import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.time.OffsetDateTime;
@@ -20,6 +22,7 @@
 import java.util.List;
 import java.util.Set;
 import javax.inject.Inject;
+import javax.json.JsonObject;
 import javax.json.spi.JsonProvider;
 import javax.transaction.Transactional;
 import javax.validation.constraints.NotEmpty;
@@ -31,8 +34,10 @@
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import org.jboss.logging.Logger;
+import org.jsoup.Jsoup;
 
 @Path("/bookmarks")
 public class BookmarkResource {
@@ -75,9 +80,7 @@
                   user.id)
               .list();
     }
-    return bookmarkList
-        .data("bookmarks", bookmarks)
-        .data("authenticated", !identity.isAnonymous());
+    return bookmarkList.data("bookmarks", bookmarks).data("authenticated", !identity.isAnonymous());
   }
 
   @POST
@@ -87,7 +90,8 @@
       @FormParam("uri") URI uri,
       @FormParam("title") @NotEmpty String title,
       @FormParam("description") String description,
-      @FormParam("visibility") @NotNull @Pattern(regexp = "public|semiprivate|private") String visibility)
+      @FormParam("visibility") @NotNull @Pattern(regexp = "public|semiprivate|private")
+          String visibility)
       throws URISyntaxException {
 
     var userName = identity.getPrincipal().getName();
@@ -116,6 +120,15 @@
     return Response.seeOther(new URI("/bookmarks")).build();
   }
 
+  @GET
+  @Path("page-info")
+  @Authenticated
+  @Produces(APPLICATION_JSON)
+  public JsonObject getPageInfo(@QueryParam("uri") URI uri) throws IOException {
+    var document = Jsoup.connect(uri.toString()).get();
+    return jsonProvider.createObjectBuilder().add("title", document.title()).build();
+  }
+
   @TemplateExtension
   static String humanDateTime(TemporalAccessor x) {
     return humanDateFormatter.format(x);
diff --git a/src/main/resources/META-INF/resources/bookmarks/bookmarkList.js b/src/main/resources/META-INF/resources/bookmarks/bookmarkList.js
index 11c4b10..4c8287a 100644
--- a/src/main/resources/META-INF/resources/bookmarks/bookmarkList.js
+++ b/src/main/resources/META-INF/resources/bookmarks/bookmarkList.js
@@ -1,5 +1,6 @@
 document.addEventListener('DOMContentLoaded', () => {
-  let bookmarkSubmissionPane = document.getElementById('bookmark-submission-pane');
+  let bookmarkSubmissionPane = document.getElementById(
+      'bookmark-submission-pane');
   let titleInput = document.getElementById('title-input');
   let uriInput = document.getElementById('uri-input');
   let uriSpinner = document.getElementById('uri-spinner');
@@ -9,7 +10,10 @@
   uriInput.addEventListener('blur', async () => {
     uriSpinner.hidden = false;
     uriSpinner.playing = true;
-    let r = await fetch(uriInput.value);
+    let searchParams = new URLSearchParams({'uri': uriInput.value});
+    console.log(`/bookmarks/page-info?${searchParams}`);
+    let fetchUrl = new URL(`/bookmarks/page-info?${searchParams}`, document.URL);
+    let r = await fetch(fetchUrl);
     uriSpinner.hidden = true;
     uriSpinner.playing = false;
 
@@ -17,14 +21,8 @@
       return;
     }
 
-    let html = await r.text();
-    let parser = new DOMParser();
-    let doc = parser.parseFromString(html, "text/html");
-    let titles = doc.getElementsByTagName('title');
-    if (titles.length <= 0) {
-      return;
-    }
-    titleInput.value = titles[0].innerText;
+    let pageInfo = await r.json();
+    titleInput.value = pageInfo.title;
   });
 });