move minio webhook into new photostorage app
diff --git a/apps/photostorage/cmd/cmd b/apps/photostorage/cmd/cmd
new file mode 100755
index 0000000..b8ef90a
--- /dev/null
+++ b/apps/photostorage/cmd/cmd
Binary files differ
diff --git a/apps/photostorage/cmd/main.go b/apps/photostorage/cmd/main.go
new file mode 100644
index 0000000..4f1e4af
--- /dev/null
+++ b/apps/photostorage/cmd/main.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"net/http"
+
+	"github.com/giolekva/pcloud/apps/photostorage"
+)
+
+var port = flag.Int("port", 123, "Port to listen on.")
+var apiAddr = flag.String("api_addr", "http://localhost/graphql", "PCloud GraphQL API server address.")
+
+func main() {
+	flag.Parse()
+	http.Handle("/new_object", &photostorage.Handler{*apiAddr})
+	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
+}
diff --git a/apps/photostorage/cover.out b/apps/photostorage/cover.out
new file mode 100644
index 0000000..920d9cf
--- /dev/null
+++ b/apps/photostorage/cover.out
@@ -0,0 +1,23 @@
+mode: set
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:29.48,31.16 2 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:34.2,35.18 2 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:38.2,39.16 2 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:42.2,42.56 1 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:31.16,33.3 1 0
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:35.18,37.3 1 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:39.16,41.3 1 0
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:49.69,51.16 2 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:56.2,56.20 1 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:60.2,63.16 4 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:68.2,70.16 3 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:73.2,74.16 2 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:79.2,79.38 1 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:84.2,85.16 2 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:88.2,88.29 1 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:51.16,55.3 3 0
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:56.20,59.3 1 0
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:63.16,67.3 3 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:70.16,71.13 1 0
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:74.16,78.3 3 0
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:79.38,83.3 3 1
+github.giolekva/pcloud/apps/photo-storage/minio_importer.go:85.16,86.13 1 0
diff --git a/apps/photostorage/minio_importer.go b/apps/photostorage/minio_importer.go
new file mode 100644
index 0000000..11d1845
--- /dev/null
+++ b/apps/photostorage/minio_importer.go
@@ -0,0 +1,89 @@
+package photostorage
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/golang/glog"
+	"github.com/itaysk/regogo"
+)
+
+var jsonContentType = "application/json"
+
+var addImgTmpl = `mutation {
+  addImage(input: [{ objectPath: %s }]) {
+    image {
+      id
+    }
+  }
+}`
+
+type Query struct {
+	Query string
+}
+
+func EventToQuery(event string) (Query, error) {
+	key, err := regogo.Get(event, "input.Key")
+	if err != nil {
+		return Query{}, err
+	}
+	keyStr := key.String()
+	if keyStr == "" {
+		return Query{}, errors.New("Key not found")
+	}
+	objectPath, err := json.Marshal(key.String())
+	if err != nil {
+		return Query{}, err
+	}
+	return Query{fmt.Sprintf(addImgTmpl, objectPath)}, nil
+}
+
+type Handler struct {
+	ApiAddr string
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		glog.Error(err)
+		http.Error(w, "Could not read HTTP request body", http.StatusBadRequest)
+		return
+	}
+	if len(body) == 0 {
+		// Just a health check from Minio
+		return
+	}
+	bodyStr := string(body)
+	glog.Infof("Received event from Minio: %s", bodyStr)
+	query, err := EventToQuery(bodyStr)
+	if err != nil {
+		glog.Error(err)
+		http.Error(w, "INTERNAL", http.StatusBadRequest)
+		return
+	}
+	glog.Info(query)
+	queryJson, err := json.Marshal(query)
+	if err != nil {
+		panic(err)
+	}
+	resp, err := http.Post(h.ApiAddr, jsonContentType, bytes.NewReader(queryJson))
+	if err != nil {
+		glog.Error(err)
+		http.Error(w, "Query failed", http.StatusInternalServerError)
+		return
+	}
+	if resp.StatusCode != http.StatusOK {
+		glog.Error(resp.StatusCode)
+		http.Error(w, "Query failed", resp.StatusCode)
+		return
+	}
+	respBody, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		panic(err)
+	}
+	glog.Info(string(respBody))
+}
diff --git a/apps/photostorage/minio_importer_test.go b/apps/photostorage/minio_importer_test.go
new file mode 100644
index 0000000..5741ebe
--- /dev/null
+++ b/apps/photostorage/minio_importer_test.go
@@ -0,0 +1,116 @@
+package photostorage
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+)
+
+func checkErr(err error, t *testing.T) {
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+type OkHandler struct {
+}
+
+func (h *OkHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+
+}
+
+type ErrorHandler struct {
+}
+
+func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	http.Error(w, "", http.StatusBadRequest)
+}
+
+func TestValidEvent(t *testing.T) {
+	q, err := EventToQuery(`{"Key": "foo/bar"}`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := `mutation {
+  addImage(input: [{ objectPath: "foo/bar" }]) {
+    image {
+      id
+    }
+  }
+}`
+	if q.Query != expected {
+		t.Fatal(q.Query)
+	}
+}
+
+func TestValidEventEscaping(t *testing.T) {
+	q, err := EventToQuery(`{"Key": "foo\"bar"}`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := `mutation {
+  addImage(input: [{ objectPath: "foo\"bar" }]) {
+    image {
+      id
+    }
+  }
+}`
+	if q.Query != expected {
+		t.Fatal(q.Query)
+	}
+}
+
+func TestNoKey(t *testing.T) {
+	_, err := EventToQuery(`{"foo": "bar"}`)
+	if err == nil {
+		t.Fatal("Got key")
+	}
+}
+
+func TestInvalidKey(t *testing.T) {
+	_, err := EventToQuery(`{"foo": 123}`)
+	if err == nil {
+		t.Fatal("Got key")
+	}
+}
+
+func TestInvalidKeyComplex(t *testing.T) {
+	_, err := EventToQuery(`{"foo": {"bar": 5}}`)
+	if err == nil {
+		t.Fatal("Got key")
+	}
+}
+
+func TestHandlerOk(t *testing.T) {
+	mockApi := httptest.NewServer(&OkHandler{})
+	r, err := http.NewRequest("GET", "/foo", strings.NewReader(`{"Key": "foo/bar"}`))
+	checkErr(err, t)
+	rec := httptest.NewRecorder()
+	(&Handler{mockApi.URL}).ServeHTTP(rec, r)
+	if rec.Code != http.StatusOK {
+		t.Fatal(rec.Code)
+	}
+}
+
+func TestHandlerInvalidEvent(t *testing.T) {
+	mockApi := httptest.NewServer(&OkHandler{})
+	r, err := http.NewRequest("GET", "/foo", strings.NewReader(`{"Key": 123}`))
+	checkErr(err, t)
+	rec := httptest.NewRecorder()
+	(&Handler{mockApi.URL}).ServeHTTP(rec, r)
+	if rec.Code != http.StatusBadRequest {
+		t.Fatal(rec.Code)
+	}
+}
+
+func TestHandlerError(t *testing.T) {
+	mockApi := httptest.NewServer(&ErrorHandler{})
+	r, err := http.NewRequest("GET", "/foo", strings.NewReader(`{"Key": "foo/bar"}`))
+	checkErr(err, t)
+	rec := httptest.NewRecorder()
+	(&Handler{mockApi.URL}).ServeHTTP(rec, r)
+	if rec.Code == http.StatusOK {
+		t.Fatal(rec.Code)
+	}
+}
diff --git a/controller/cmd/main.go b/controller/cmd/main.go
new file mode 100644
index 0000000..af2e019
--- /dev/null
+++ b/controller/cmd/main.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+
+	"github.com/golang/glog"
+	"github.com/itaysk/regogo"
+)
+
+var port = flag.Int("port", 123, "Port to listen on.")
+var apiAddr = flag.String("api_addr", "", "PCloud GraphQL API server address.")
+
+var jsonContentType = "application/json"
+
+var addImgTmpl = `
+mutation {
+  addImage(input: [%s]) {
+    image {
+      id
+    }
+  }
+}`
+
+type image struct {
+	ObjectPath string
+}
+
+type query struct {
+	Query string
+}
+
+func minioHandler(w http.ResponseWriter, r *http.Request) {
+	body, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		glog.Error(err)
+		http.Error(w, "Could not read HTTP request body", http.StatusInternalServerError)
+		return
+	}
+	if len(body) == 0 {
+		// Just a health check from Minio
+		return
+	}
+	bodyStr := string(body)
+	glog.Infof("Received event from Minio: %s", bodyStr)
+	key, err := regogo.Get(bodyStr, "input.Key")
+	if err != nil {
+		glog.Error(err)
+		http.Error(w, "Could not find object key", http.StatusBadRequest)
+		return
+	}
+	img := image{key.String()}
+	imgJson, err := json.Marshal(img)
+	if err != nil {
+		panic(err)
+	}
+	q := query{fmt.Sprintf(addImgTmpl, imgJson)}
+	glog.Info(q)
+	queryJson, err := json.Marshal(q)
+	if err != nil {
+		panic(err)
+	}
+	resp, err := http.Post(
+		*apiAddr,
+		jsonContentType,
+		bytes.NewReader(queryJson))
+	if err != nil {
+		glog.Error(err)
+		http.Error(w, "Query failed", http.StatusInternalServerError)
+		return
+	}
+	respBody, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		panic(err)
+	}
+	glog.Info(string(respBody))
+}
+
+func main() {
+	flag.Parse()
+
+	http.HandleFunc("/minio_webhook", minioHandler)
+	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
+}
diff --git a/controller/controller b/controller/controller
new file mode 100755
index 0000000..f2995db
--- /dev/null
+++ b/controller/controller
Binary files differ
diff --git a/controller/go.mod b/controller/go.mod
index 955f4b8..c372a57 100644
--- a/controller/go.mod
+++ b/controller/go.mod
@@ -7,5 +7,7 @@
 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
 	github.com/itaysk/regogo v0.0.0-20200418072509-74b59e1875c2
 	github.com/vektah/gqlparser v1.3.1
+	k8s.io/api v0.18.2
+	k8s.io/apimachinery v0.18.2
 	k8s.io/client-go v0.18.2
 )
diff --git a/controller/main.go b/controller/main.go
index 70e4af0..11498ad 100644
--- a/controller/main.go
+++ b/controller/main.go
@@ -39,30 +39,8 @@
 }
 
 func (m *MinioWebhook) minioHandler(w http.ResponseWriter, r *http.Request) {
-	body, err := ioutil.ReadAll(r.Body)
-	if err != nil {
-		glog.Error(err)
-		http.Error(w, "Could not read HTTP request body", http.StatusInternalServerError)
-		return
-	}
-	if len(body) == 0 {
-		return
-	}
-	glog.Infof("Received event from Minio: %s", string(body))
-	key, err := regogo.Get(string(body), "input.Key")
-	if err != nil {
-		glog.Error(err)
-		http.Error(w, "Could not find object key", http.StatusBadRequest)
-		return
-	}
-	resp, err := m.gql.RunQuery(fmt.Sprintf(
-		"mutation { addImage(input: [{objectPath: \"%s\"}]) { image { id }} }",
-		key.String()))
-	if err != nil {
-		glog.Error(err)
-		http.Error(w, "Can not add given objects", http.StatusInternalServerError)
-		return
-	}
+	// TODO(giolekva): move this to events processor
+	resp := ""
 	id, err := regogo.Get(resp, "input.addImage.image[0].id")
 	if err != nil {
 		glog.Error(err)
@@ -196,7 +174,6 @@
 		panic(err)
 	}
 	mw := MinioWebhook{gqlClient, pods}
-	http.HandleFunc("/minio_webhook", mw.minioHandler)
 	http.HandleFunc("/graphql", mw.graphqlHandler)
 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
 }