e2e face recognition
diff --git a/photos-ui/main.go b/photos-ui/main.go
index 879ef0b..97e0126 100644
--- a/photos-ui/main.go
+++ b/photos-ui/main.go
@@ -5,27 +5,57 @@
 	"fmt"
 	"log"
 	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"text/template"
 )
 
-var port = flag.Int("port", 3000, "Port to listen on")
+var port = flag.Int("port", 3000, "Port to listen on.")
+var pcloudApiServer = flag.String("pcloud_api_server", "", "PCloud API Server address.")
 
 func handle_gallery(w http.ResponseWriter, r *http.Request) {
 	http.ServeFile(w, r, "./gallery.html")
 }
 
 func handle_photo(w http.ResponseWriter, r *http.Request) {
-	http.ServeFile(w, r, "./photo.html")
+	err := r.ParseForm()
+	if err != nil {
+		http.Error(w, "Could not read query", http.StatusInternalServerError)
+		return
+	}
+	id, ok := r.Form["id"]
+	if !ok {
+		http.Error(w, "Photo id must be provided", http.StatusBadRequest)
+		return
+	}
+	t, err := template.ParseFiles("photo.html")
+	if err != nil {
+		log.Print(err)
+		http.Error(w, "Could not process page", http.StatusInternalServerError)
+		return
+	}
+	err = t.Execute(w, struct{ Id string }{id[0]})
+	if err != nil {
+		log.Print(err)
+		http.Error(w, "Could not process page", http.StatusInternalServerError)
+		return
+	}
 }
 
-func handle_graphql(w http.ResponseWriter, r *http.Request) {
-	http.Redirect(w, r, "http://localhost:8080/graphql?query={queryImage(){id objectPath}}", http.StatusMovedPermanently)
+func newGqlProxy(pcloudApiServer string) *httputil.ReverseProxy {
+	u, err := url.Parse(pcloudApiServer)
+	if err != nil {
+		panic(err)
+	}
+	return httputil.NewSingleHostReverseProxy(u)
 }
 
 func main() {
 	flag.Parse()
 	fs := http.FileServer(http.Dir("./static"))
 	http.Handle("/static/", http.StripPrefix("/static/", fs))
-	http.HandleFunc("/graphql", handle_graphql)
+	http.Handle("/graphql", newGqlProxy(*pcloudApiServer))
+	http.HandleFunc("/photo", handle_photo)
 	http.HandleFunc("/", handle_gallery)
 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
 }
diff --git a/photos-ui/photo.html b/photos-ui/photo.html
index 8628549..d41bb29 100644
--- a/photos-ui/photo.html
+++ b/photos-ui/photo.html
@@ -4,7 +4,8 @@
         <title>Photos</title>
     </head>
     <script src="static/photos.js"></script>
-    <body onload="initPhoto('gallery')">
-      <div id="gallery" />
+    <body onload="initImg('photo', '{{ .Id }}')">
+      <img id="photo" onload="drawFaces('photo', 'faces', '{{ .Id }}')" src=""></img>
+      <canvas id="faces"></canvas>
     </body>
 </html>
diff --git a/photos-ui/static/photos.js b/photos-ui/static/photos.js
index ccadb4b..6f46a19 100644
--- a/photos-ui/static/photos.js
+++ b/photos-ui/static/photos.js
@@ -1,7 +1,29 @@
 async function fetchAllPhotos() {
-    return await fetch("/graphql?query={queryImage(){objectPath}}")
+    return await fetch("/graphql?query={queryImage(){id objectPath}}")
 	.then(resp => resp.json())
-	.then(resp => resp.data.queryImage)
+	.then(resp => resp.queryImage)
+	.catch(error => {
+	    alert(error);
+	    return [];
+	});
+    
+}
+
+async function fetchImage(id) {
+    return await fetch("/graphql?query={getImage(id: \"" + id + "\"){id objectPath}}")
+	.then(resp => resp.json())
+	.then(resp => resp.getImage)
+	.catch(error => {
+	    alert(error);
+	    return {};
+	});
+    
+}
+
+async function fetchAllImageSegments(id) {
+    return await fetch("/graphql?query={getImage(id: \"" + id + "\"){segments { upperLeftX upperLeftY lowerRightX lowerRightY }}}")    
+	.then(resp => resp.json())
+	.then(resp => resp.getImage.segments)
 	.catch(error => {
 	    alert(error);
 	    return [];
@@ -11,12 +33,38 @@
 
 async function initGallery(gallery_elem_id) {
     imgs = await fetchAllPhotos();
-    console.log(imgs);
     img_list = "<ul>";
     for (img of imgs) {
-	img_list += "<li><a href='/photo/" + img.id + "'><img style='max-width: 300px' src='http://localhost:9000/" + img.objectPath + "' /></a></li>";
+	img_list += "<li><a href='/photo?id=" + img.id + "'><img style='max-width: 300px' src='http://localhost:9000/" + img.objectPath + "' /></a></li>";
     }
     img_list += "</ul>";
-    console.log(img_list);
     document.getElementById(gallery_elem_id).innerHTML = img_list;
 }
+
+async function initImg(img_elem_id, id) {
+    img = await fetchImage(id);
+    document.getElementById(img_elem_id).setAttribute("src", "http://localhost:9000/" + img.objectPath);
+}
+
+async function drawFaces(photo_elem_id, faces_canvas_elem_id, id){
+    console.log(id);
+    faces = await fetchAllImageSegments(id);
+    
+    var img = document.getElementById(photo_elem_id);
+    var cnvs = document.getElementById(faces_canvas_elem_id);
+    
+    cnvs.style.position = "absolute";
+    cnvs.style.left = img.offsetLeft + "px";
+    cnvs.style.top = img.offsetTop + "px";
+    cnvs.width = img.width;
+    cnvs.height = img.height;
+    
+    var ctx = cnvs.getContext("2d");
+    for (f of faces) {
+	ctx.beginPath();
+	ctx.lineWidth = 2;
+	ctx.strokeStyle = 'red';    
+	ctx.rect(f.upperLeftX, f.upperLeftY, f.lowerRightX - f.upperLeftX, f.lowerRightY - f.upperLeftY);
+	ctx.stroke();
+    }
+}