From c61badab4bcf48e53d25feda22a80e902b983da5 Mon Sep 17 00:00:00 2001 From: Dominic Wong Date: Thu, 9 Dec 2021 10:56:42 +0000 Subject: [PATCH] Space API (#290) --- email/handler/email_test.go | 67 +++ go.mod | 5 +- go.sum | 26 +- space/Makefile | 4 +- space/README.md | 4 +- space/examples.json | 85 ++- space/handler/space.go | 306 ++++++++++- space/handler/space_test.go | 534 ++++++++++++++++++ space/main.go | 18 +- space/proto/space.pb.go | 981 ++++++++++++++++++++++++++++++++-- space/proto/space.pb.micro.go | 101 +++- space/proto/space.proto | 100 +++- space/publicapi.json | 4 +- 13 files changed, 2126 insertions(+), 109 deletions(-) create mode 100644 email/handler/email_test.go create mode 100644 space/handler/space_test.go diff --git a/email/handler/email_test.go b/email/handler/email_test.go new file mode 100644 index 0000000..5374f75 --- /dev/null +++ b/email/handler/email_test.go @@ -0,0 +1,67 @@ +package handler + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestEmailValidation(t *testing.T) { + g := NewWithT(t) + tcs := []struct { + name string + email string + valid bool + }{ + { + name: "Empty", + email: "", + valid: false, + }, + { + name: "Normal email", + email: "joe@example.com", + valid: true, + }, + { + name: "Email with dots", + email: "joe.bloggs@example.com", + valid: true, + }, + { + name: "Email with plus", + email: "joe+1@example.com", + valid: true, + }, + { + name: "Email with underscores", + email: "joe_bloggs@example.com", + valid: true, + }, + { + name: "White space", + email: "joe bloggs@example.com", + valid: false, + }, + { + name: "Trailing whitespace", + email: "joe@example.com ", + valid: false, + }, + { + name: "Preceding whitespace", + email: " joe@example.com", + valid: false, + }, + { + name: "Normal email", + email: "joe@example.com", + valid: true, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + g.Expect(validEmail(tc.email)).To(Equal(tc.valid)) + }) + } +} diff --git a/go.mod b/go.mod index e81b6fe..fc4125e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/SlyMarbo/rss v1.0.1 github.com/Teamwork/spamc v0.0.0-20200109085853-a4e0c5c3f7a0 github.com/asim/mq v0.1.0 + github.com/aws/aws-sdk-go v1.42.17 github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598 // indirect github.com/cdipaolo/sentiment v0.0.0-20200617002423-c697f64e7f10 github.com/crufter/lexer v0.0.0-20120907053443-23fe8c7add01 @@ -31,6 +32,8 @@ require ( github.com/mattheath/kala v0.0.0-20171219141654-d6276794bf0e github.com/micro/micro/v3 v3.8.0 github.com/miekg/dns v1.1.31 // indirect + github.com/minio/minio-go/v7 v7.0.16 + github.com/onsi/gomega v1.10.5 github.com/oschwald/geoip2-golang v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/paulmach/go.geo v0.0.0-20180829195134-22b514266d33 @@ -50,7 +53,7 @@ require ( github.com/ttacon/libphonenumber v1.2.1 // indirect go.mongodb.org/mongo-driver v1.7.2 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 - golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 + golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 google.golang.org/api v0.59.0 google.golang.org/grpc/examples v0.0.0-20211103202053-3b94303f3754 // indirect diff --git a/go.sum b/go.sum index 859d75a..7677398 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/asim/mq v0.1.0 h1:v/gg/blpqXDStNYveq6bGEhT1DQrH1Iaxd5srMjszX0= github.com/asim/mq v0.1.0/go.mod h1:gSmd9l3K8i9qM440G4YXwc2Zjn9Y77+AImRc3GmjK78= github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.42.17 h1:NEMRZcLd+YhXhUqdjwqNGtEYthiUZ+3BudGmK4/0yaA= +github.com/aws/aws-sdk-go v1.42.17/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= @@ -408,10 +410,15 @@ github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -428,8 +435,9 @@ github.com/kevinburke/rest v0.0.0-20210506044642-5611499aa33c/go.mod h1:pD+iEcdA github.com/kevinburke/twilio-go v0.0.0-20210327194925-1623146bcf73 h1:PSsFm2SRpq9LnaRHLz4u9ZZ3liWjgXM6OMxXE4/qlgY= github.com/kevinburke/twilio-go v0.0.0-20210327194925-1623146bcf73/go.mod h1:Fm9alkN1/LPVY1eqD/psyMwPWE4VWl4P01/nTYZKzBk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= @@ -483,14 +491,16 @@ github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KK github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/micro/micro/v3 v3.7.1-0.20211111170433-1ebb8328e280 h1:SOthdbPABgdxgsi9d7lXBDE8WzlTvj+2owHR51vW2AA= -github.com/micro/micro/v3 v3.7.1-0.20211111170433-1ebb8328e280/go.mod h1:NqYnFOGrnc0Apk912w49oX9qIk1YDJcCaO+y+CLaAXA= github.com/micro/micro/v3 v3.8.0 h1:RTH2835RJ4/aqLZGMjGCIf7HroCmYlJh2KRHHuSL/AE= github.com/micro/micro/v3 v3.8.0/go.mod h1:gjFa8T2ouD6BvorPTVPXLWtrRJwSKT5KxUNuu23Kkts= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= +github.com/minio/minio-go/v7 v7.0.16 h1:GspaSBS8lOuEUCAqMe0W3UxSoyOA4b4F8PTspRVI+k4= +github.com/minio/minio-go/v7 v7.0.16/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g= +github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -604,11 +614,13 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -715,6 +727,7 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -810,8 +823,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -872,6 +886,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -889,6 +904,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -913,6 +929,7 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1173,6 +1190,7 @@ gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdOD gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/space/Makefile b/space/Makefile index fea1953..8f66801 100644 --- a/space/Makefile +++ b/space/Makefile @@ -1,5 +1,7 @@ GOPATH:=$(shell go env GOPATH) +MODIFY=Mproto/imports/api.proto=github.com/micro/micro/v3/proto/api + .PHONY: init init: go get -u github.com/golang/protobuf/proto @@ -13,7 +15,7 @@ api: .PHONY: proto proto: - protoc --proto_path=. --micro_out=. --go_out=:. proto/space.proto + protoc --proto_path=. --micro_out=${MODIFY}:. --go_out=${MODIFY}:. proto/space.proto .PHONY: build build: diff --git a/space/README.md b/space/README.md index 03aee2a..36a4062 100644 --- a/space/README.md +++ b/space/README.md @@ -3,5 +3,7 @@ Infinite cloud storage # Space Service Space for simple object storage. Put anything in the cloud -forever. Backed by S3 compatible storage API. +forever. Objects can be public (readable by all via a public URL) or private. + +Powered by S3 compatible storage API. diff --git a/space/examples.json b/space/examples.json index e957989..9b7592d 100644 --- a/space/examples.json +++ b/space/examples.json @@ -1,13 +1,90 @@ { - "vote": [ + "create": [ { - "title": "Vote for the API", + "title": "Create an object", "run_check": false, "request": { - "message": "Launch it!" + "object": "", + "name": "images/file.jpg", + "visibility": "public" + }, + "response": { + "url": "https://example.com/foo/bar/file.jpg" + } + } + ], + "update": [ + { + "title": "Update an object", + "run_check": false, + "request": { + "object": "", + "name": "images/file.jpg", + "visibility": "public" + }, + "response": { + "url": "https://example.com/foo/bar/images/file.jpg" + } + } + ], + "delete": [ + { + "title": "Delete an object", + "run_check": false, + "request": { + "name": "images/file.jpg" + }, + "response": { + } + } + ], + "list": [ + { + "title": "List objects with prefix", + "run_check": false, + "request": { + "prefix": "images/" + }, + "response": { + "objects": [ + { + "name": "images/file.jpg", + "modified": 1638549232, + "url": "https://example.com/foo/bar/images/file.jpg" + }, + { + "name": "images/file2.jpg", + "modified": 1638547232, + "url": "https://example.com/foo/bar/images/file2.jpg" + } + ] + } + } + ], + "head": [ + { + "title": "Head an object", + "run_check": false, + "request": { + "name": "images/file.jpg" + }, + "response": { + "name": "images/file.jpg", + "modified": 1638549232, + "created": 1638546232, + "url": "https://example.com/foo/bar/images/file.jpg", + "visibility": "public" + } + } + ], + "read": [ + { + "title": "Read an object", + "run_check": false, + "request": { + "name": "images/file.jpg" }, "response": { - "message": "Thanks for the vote!" } } ] diff --git a/space/handler/space.go b/space/handler/space.go index 783e766..800bbd6 100644 --- a/space/handler/space.go +++ b/space/handler/space.go @@ -1,50 +1,306 @@ package handler import ( + "bytes" "context" - "sync" + "encoding/json" + "fmt" + "strconv" + "strings" "time" - + "github.com/micro/micro/v3/proto/api" + "github.com/micro/micro/v3/service" + "github.com/micro/micro/v3/service/config" + "github.com/micro/micro/v3/service/errors" + log "github.com/micro/micro/v3/service/logger" "github.com/micro/services/pkg/tenant" - "github.com/micro/micro/v3/service/store" pb "github.com/micro/services/space/proto" + "github.com/minio/minio-go/v7/pkg/s3utils" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + awscreds "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + sthree "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" ) -type Space struct{} +const ( + mdACL = "X-Amz-Acl" + mdACLPublic = "public-read" + mdCreated = "Micro-Created" + mdVisibility = "Micro-Visibility" -var ( - mtx sync.RWMutex - - voteKey = "votes/" + visibilityPrivate = "private" + visibilityPublic = "public" ) -type Vote struct { - Id string `json:"id"` - Message string `json:"message"` - VotedAt time.Time `json:"voted_at"` +type Space struct { + conf conf + client s3iface.S3API } -func (n *Space) Vote(ctx context.Context, req *pb.VoteRequest, rsp *pb.VoteResponse) error { - mtx.Lock() - defer mtx.Unlock() +type conf struct { + AccessKey string `json:"access_key"` + SecretKey string `json:"secret_key"` + Endpoint string `json:"endpoint"` + SpaceName string `json:"space_name"` + SSL bool `json:"ssl"` + Region string `json:"region"` + BaseURL string `json:"base_url"` +} - id, ok := tenant.FromContext(ctx) - if !ok { - id = "micro" +func NewSpace(srv *service.Service) *Space { + var c conf + val, err := config.Get("micro.space") + if err != nil { + log.Fatalf("Failed to load config %s", err) + } + if err := val.Scan(&c); err != nil { + log.Fatalf("Failed to load config %s", err) } - rec := store.NewRecord(voteKey + id, &Vote{ - Id: id, - Message: req.Message, - VotedAt: time.Now(), + sess := session.Must(session.NewSession(&aws.Config{ + Endpoint: &c.Endpoint, + Region: &c.Region, + Credentials: awscreds.NewStaticCredentials(c.AccessKey, c.SecretKey, ""), + })) + client := sthree.New(sess) + + // make sure this thing exists + if _, err := client.CreateBucket(&sthree.CreateBucketInput{ + Bucket: aws.String(c.SpaceName), + }); err != nil && + (!strings.Contains(err.Error(), "already exists") && !strings.Contains(err.Error(), "not empty")) { + log.Fatalf("Error making bucket %s", err) + } + + return &Space{ + conf: c, + client: client, + } +} + +func (s Space) Create(ctx context.Context, request *pb.CreateRequest, response *pb.CreateResponse) error { + var err error + response.Url, err = s.upsert(ctx, request.Object, request.Name, request.Visibility, "space.Create", true) + return err +} + +func (s Space) upsert(ctx context.Context, object []byte, name, visibility, method string, create bool) (string, error) { + tnt, ok := tenant.FromContext(ctx) + if !ok { + return "", errors.Unauthorized(method, "Unauthorized") + } + if len(name) == 0 { + return "", errors.BadRequest(method, "Missing name param") + } + objectName := fmt.Sprintf("%s/%s", tnt, name) + if err := s3utils.CheckValidObjectName(objectName); err != nil { + return "", errors.BadRequest(method, "Invalid name") + } + + exists := false + hoo, err := s.client.HeadObject(&sthree.HeadObjectInput{ + Bucket: aws.String(s.conf.SpaceName), + Key: aws.String(objectName), }) + if err != nil { + aerr, ok := err.(awserr.Error) + if !ok || aerr.Code() != "NotFound" { + return "", errors.InternalServerError(method, "Error creating object") + } + } else { + exists = true + } - // we don't need to check the error - store.Write(rec) + if create && exists { + return "", errors.BadRequest(method, "Object already exists") + } - rsp.Message = "Thanks for the vote!" + createTime := aws.String(fmt.Sprintf("%d", time.Now().Unix())) + if exists { + createTime = hoo.Metadata[mdCreated] + } + + if len(visibility) == 0 { + visibility = visibilityPrivate + } + putInput := &sthree.PutObjectInput{ + Body: bytes.NewReader(object), + Key: aws.String(objectName), + Bucket: aws.String(s.conf.SpaceName), + Metadata: map[string]*string{ + mdVisibility: aws.String(visibility), + mdCreated: createTime, + }, + } + // TODO flesh out options - might want to do content-type for better serving of object + if visibility == visibilityPublic { + putInput.ACL = aws.String(mdACLPublic) + } + + if _, err := s.client.PutObject(putInput); err != nil { + log.Errorf("Error creating object %s", err) + return "", errors.InternalServerError(method, "Error creating object") + } + + // TODO fix the url + return fmt.Sprintf("%s/%s", s.conf.BaseURL, objectName), nil + +} + +func (s Space) Update(ctx context.Context, request *pb.UpdateRequest, response *pb.UpdateResponse) error { + var err error + response.Url, err = s.upsert(ctx, request.Object, request.Name, request.Visibility, "space.Update", false) + return err +} + +func (s Space) Delete(ctx context.Context, request *pb.DeleteRequest, response *pb.DeleteResponse) error { + method := "space.Delete" + tnt, ok := tenant.FromContext(ctx) + if !ok { + return errors.Unauthorized(method, "Unauthorized") + } + if len(request.Name) == 0 { + return errors.BadRequest(method, "Missing name param") + } + objectName := fmt.Sprintf("%s/%s", tnt, request.Name) + if _, err := s.client.DeleteObject(&sthree.DeleteObjectInput{ + Bucket: aws.String(s.conf.SpaceName), + Key: aws.String(objectName), + }); err != nil { + log.Errorf("Error deleting object %s", err) + return errors.InternalServerError(method, "Error deleting object") + } + return nil +} + +func (s Space) List(ctx context.Context, request *pb.ListRequest, response *pb.ListResponse) error { + method := "space.List" + tnt, ok := tenant.FromContext(ctx) + if !ok { + return errors.Unauthorized(method, "Unauthorized") + } + objectName := fmt.Sprintf("%s/%s", tnt, request.Prefix) + rsp, err := s.client.ListObjects(&sthree.ListObjectsInput{ + Bucket: aws.String(s.conf.SpaceName), + Prefix: aws.String(objectName), + }) + if err != nil { + log.Errorf("Error listing objects %s", err) + return errors.InternalServerError(method, "Error listing objects") + } + response.Objects = []*pb.ListObject{} + for _, oi := range rsp.Contents { + response.Objects = append(response.Objects, &pb.ListObject{ + Name: strings.TrimPrefix(*oi.Key, tnt+"/"), + Modified: oi.LastModified.Unix(), + Url: fmt.Sprintf("%s/%s", s.conf.BaseURL, *oi.Key), + }) + } + return nil +} + +func (s Space) Head(ctx context.Context, request *pb.HeadRequest, response *pb.HeadResponse) error { + method := "space.Head" + tnt, ok := tenant.FromContext(ctx) + if !ok { + return errors.Unauthorized(method, "Unauthorized") + } + if len(request.Name) == 0 { + return errors.BadRequest(method, "Missing name param") + } + objectName := fmt.Sprintf("%s/%s", tnt, request.Name) + + goo, err := s.client.HeadObject(&sthree.HeadObjectInput{ + Bucket: aws.String(s.conf.SpaceName), + Key: aws.String(objectName), + }) + if err != nil { + aerr, ok := err.(awserr.Error) + if ok && aerr.Code() == "NotFound" { + return errors.BadRequest(method, "Object not found") + } + log.Errorf("Error s3 %s", err) + return errors.InternalServerError(method, "Error reading object") + } + + vis := visibilityPrivate + if md, ok := goo.Metadata[mdVisibility]; ok && len(*md) > 0 { + vis = *md + } + var created int64 + if md, ok := goo.Metadata[mdCreated]; ok && len(*md) > 0 { + created, err = strconv.ParseInt(*md, 10, 64) + if err != nil { + log.Errorf("Error %s", err) + } + } + + response.Object = &pb.HeadObject{ + Name: request.Name, + Modified: goo.LastModified.Unix(), + Created: created, + Visibility: vis, + Url: fmt.Sprintf("%s/%s", s.conf.BaseURL, objectName), + } return nil } +func (s *Space) Read(ctx context.Context, req *api.Request, rsp *api.Response) error { + method := "space.Read" + tnt, ok := tenant.FromContext(ctx) + if !ok { + return errors.Unauthorized(method, "Unauthorized") + } + var input map[string]string + if err := json.Unmarshal([]byte(req.Body), &input); err != nil { + log.Errorf("Error unmarshalling %s", err) + return errors.BadRequest(method, "Request in unexpected format") + } + name := input["name"] + if len(name) == 0 { + return errors.BadRequest(method, "Missing name param") + } + + objectName := fmt.Sprintf("%s/%s", tnt, name) + + _, err := s.client.HeadObject(&sthree.HeadObjectInput{ + Bucket: aws.String(s.conf.SpaceName), + Key: aws.String(objectName), + }) + if err != nil { + aerr, ok := err.(awserr.Error) + if ok && aerr.Code() == "NotFound" { + return errors.BadRequest(method, "Object not found") + } + log.Errorf("Error s3 %s", err) + return errors.InternalServerError(method, "Error reading object") + } + + gooreq, _ := s.client.GetObjectRequest(&sthree.GetObjectInput{ + Bucket: aws.String(s.conf.SpaceName), + Key: aws.String(objectName), + }) + urlStr, err := gooreq.Presign(5 * time.Second) + if err != nil { + aerr, ok := err.(awserr.Error) + if ok && aerr.Code() == "NoSuchKey" { + return errors.BadRequest(method, "Object not found") + } + log.Errorf("Error presigning url %s", err) + return errors.InternalServerError(method, "Error reading object") + } + rsp.Header = map[string]*api.Pair{ + "Location": { + Key: "Location", + Values: []string{urlStr}, + }, + } + rsp.StatusCode = 302 + + return nil +} diff --git a/space/handler/space_test.go b/space/handler/space_test.go new file mode 100644 index 0000000..7c3f349 --- /dev/null +++ b/space/handler/space_test.go @@ -0,0 +1,534 @@ +package handler + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + sthree "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" + "github.com/micro/micro/v3/service/auth" + "github.com/micro/micro/v3/service/errors" + pb "github.com/micro/services/space/proto" + + . "github.com/onsi/gomega" +) + +type mockS3Client struct { + s3iface.S3API + head func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) + put func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) + delete func(input *sthree.DeleteObjectInput) (*sthree.DeleteObjectOutput, error) + list func(input *sthree.ListObjectsInput) (*sthree.ListObjectsOutput, error) + get func(input *sthree.GetObjectInput) (*sthree.GetObjectOutput, error) +} + +func (m mockS3Client) HeadObject(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + if m.head != nil { + return m.head(input) + } + return &sthree.HeadObjectOutput{}, nil +} + +func (m mockS3Client) PutObject(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + if m.put != nil { + return m.put(input) + } + return &sthree.PutObjectOutput{}, nil +} + +func (m mockS3Client) DeleteObject(input *sthree.DeleteObjectInput) (*sthree.DeleteObjectOutput, error) { + if m.delete != nil { + return m.delete(input) + } + return &sthree.DeleteObjectOutput{}, nil +} + +func (m mockS3Client) ListObjects(input *sthree.ListObjectsInput) (*sthree.ListObjectsOutput, error) { + if m.list != nil { + return m.list(input) + } + return &sthree.ListObjectsOutput{}, nil +} + +func (m mockS3Client) GetObject(input *sthree.GetObjectInput) (*sthree.GetObjectOutput, error) { + if m.get != nil { + return m.get(input) + } + return &sthree.GetObjectOutput{}, nil +} + +type mockError struct { + code string + message string + err string +} + +func (m mockError) Error() string { + return m.err +} + +func (m mockError) Code() string { + return m.code +} + +func (m mockError) Message() string { + return m.message +} + +func (m mockError) OrigErr() error { + return fmt.Errorf(m.err) +} + +func TestCreate(t *testing.T) { + g := NewWithT(t) + tcs := []struct { + name string + objName string + visibility string + err error + url string + head func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) + put func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) + }{ + { + name: "Simple case", + objName: "foo.jpg", + url: "https://my-space.ams3.example.com/micro/123/foo.jpg", + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + return nil, mockError{code: "NotFound"} + }, + put: func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(input.ACL).To(BeNil()) + g.Expect(*input.Metadata[mdVisibility]).To(Equal(visibilityPrivate)) + g.Expect(input.Metadata[mdCreated]).To(Not(BeNil())) + return &sthree.PutObjectOutput{}, nil + }, + }, + { + name: "Public object", + objName: "bar/baz/foo.jpg", + visibility: "public", + url: "https://my-space.ams3.example.com/micro/123/bar/baz/foo.jpg", + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + return nil, mockError{code: "NotFound"} + }, + put: func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.ACL).To(Equal(mdACLPublic)) + g.Expect(*input.Metadata[mdVisibility]).To(Equal(visibilityPublic)) + g.Expect(input.Metadata[mdCreated]).To(Not(BeNil())) + return &sthree.PutObjectOutput{}, nil + }, + }, + { + name: "Missing name", + objName: "", + err: errors.BadRequest("space.Create", "Missing name param"), + }, + { + name: "Already exists", + objName: "foo.jpg", + err: errors.BadRequest("space.Create", "Object already exists"), + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.Key).To(Equal("micro/123/foo.jpg")) + return &sthree.HeadObjectOutput{}, nil + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + handler := Space{ + conf: conf{ + AccessKey: "access", + SecretKey: "secret", + Endpoint: "example.com", + SpaceName: "my-space", + SSL: true, + Region: "ams3", + BaseURL: "https://my-space.ams3.example.com", + }, + client: &mockS3Client{head: tc.head, put: tc.put}, + } + ctx := context.Background() + ctx = auth.ContextWithAccount(ctx, &auth.Account{ + ID: "123", + Type: "user", + Issuer: "micro", + Metadata: map[string]string{}, + Scopes: []string{"space"}, + Name: "john@example.com", + }) + rsp := pb.CreateResponse{} + err := handler.Create(ctx, &pb.CreateRequest{ + Object: []byte("foobar"), + Name: tc.objName, + Visibility: tc.visibility, + }, &rsp) + if tc.err != nil { + g.Expect(err).To(Equal(tc.err)) + } else { + g.Expect(err).To(BeNil()) + g.Expect(rsp.Url).To(Equal(tc.url)) + } + + }) + } + +} + +func TestUpdate(t *testing.T) { + g := NewWithT(t) + tcs := []struct { + name string + objName string + visibility string + err error + url string + head func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) + put func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) + }{ + { + name: "Does not exist", + objName: "foo.jpg", + url: "https://my-space.ams3.example.com/micro/123/foo.jpg", + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + return nil, mockError{code: "NotFound"} + }, + put: func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(input.ACL).To(BeNil()) + g.Expect(*input.Metadata[mdVisibility]).To(Equal(visibilityPrivate)) + g.Expect(input.Metadata[mdCreated]).To(Not(BeNil())) + return &sthree.PutObjectOutput{}, nil + }, + }, + { + name: "Does not exist. Public object", + objName: "bar/baz/foo.jpg", + visibility: "public", + url: "https://my-space.ams3.example.com/micro/123/bar/baz/foo.jpg", + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + return nil, mockError{code: "NotFound"} + }, + put: func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.ACL).To(Equal(mdACLPublic)) + g.Expect(*input.Metadata[mdVisibility]).To(Equal(visibilityPublic)) + g.Expect(input.Metadata[mdCreated]).To(Not(BeNil())) + return &sthree.PutObjectOutput{}, nil + }, + }, + { + name: "Missing name", + objName: "", + err: errors.BadRequest("space.Update", "Missing name param"), + }, + { + name: "Already exists", + objName: "foo.jpg", + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.Key).To(Equal("micro/123/foo.jpg")) + return &sthree.HeadObjectOutput{ + Metadata: map[string]*string{ + mdCreated: aws.String("1638541918"), + mdVisibility: aws.String(visibilityPrivate), + }, + }, nil + }, + put: func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(input.ACL).To(BeNil()) + g.Expect(*input.Metadata[mdVisibility]).To(Equal(visibilityPrivate)) + // created shouuld be copied from the previous + g.Expect(*input.Metadata[mdCreated]).To(Equal("1638541918")) + return &sthree.PutObjectOutput{}, nil + }, + url: "https://my-space.ams3.example.com/micro/123/foo.jpg", + }, + { + name: "Already exists public", + objName: "foo.jpg", + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.Key).To(Equal("micro/123/foo.jpg")) + return &sthree.HeadObjectOutput{ + Metadata: map[string]*string{ + mdCreated: aws.String("1638541918"), + mdVisibility: aws.String(visibilityPrivate), + }, + }, nil + }, + put: func(input *sthree.PutObjectInput) (*sthree.PutObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.ACL).To(Equal(mdACLPublic)) + g.Expect(*input.Metadata[mdVisibility]).To(Equal(visibilityPublic)) + // created shouuld be copied from the previous + g.Expect(*input.Metadata[mdCreated]).To(Equal("1638541918")) + return &sthree.PutObjectOutput{}, nil + }, + url: "https://my-space.ams3.example.com/micro/123/foo.jpg", + visibility: "public", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + handler := Space{ + conf: conf{ + AccessKey: "access", + SecretKey: "secret", + Endpoint: "example.com", + SpaceName: "my-space", + SSL: true, + Region: "ams3", + BaseURL: "https://my-space.ams3.example.com", + }, + client: &mockS3Client{head: tc.head, put: tc.put}, + } + ctx := context.Background() + ctx = auth.ContextWithAccount(ctx, &auth.Account{ + ID: "123", + Type: "user", + Issuer: "micro", + Metadata: map[string]string{}, + Scopes: []string{"space"}, + Name: "john@example.com", + }) + + rsp := pb.UpdateResponse{} + err := handler.Update(ctx, &pb.UpdateRequest{ + Object: []byte("foobar"), + Name: tc.objName, + Visibility: tc.visibility, + }, &rsp) + if tc.err != nil { + g.Expect(err).To(Equal(tc.err)) + } else { + g.Expect(err).To(BeNil()) + g.Expect(rsp.Url).To(Equal(tc.url)) + } + + }) + } + +} + +func TestDelete(t *testing.T) { + g := NewWithT(t) + tcs := []struct { + name string + objName string + err error + delete func(input *sthree.DeleteObjectInput) (*sthree.DeleteObjectOutput, error) + }{ + { + name: "Simple case", + objName: "foo.jpg", + }, + { + name: "Missing name", + objName: "", + err: errors.BadRequest("space.Delete", "Missing name param"), + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + handler := Space{ + conf: conf{ + AccessKey: "access", + SecretKey: "secret", + Endpoint: "example.com", + SpaceName: "my-space", + SSL: true, + Region: "ams3", + BaseURL: "https://my-space.ams3.example.com", + }, + client: &mockS3Client{ + delete: func(input *sthree.DeleteObjectInput) (*sthree.DeleteObjectOutput, error) { + g.Expect(input.Bucket).To(Equal(aws.String("my-space"))) + g.Expect(input.Key).To(Equal(aws.String("micro/123/" + tc.objName))) + return &sthree.DeleteObjectOutput{}, nil + }}, + } + ctx := context.Background() + ctx = auth.ContextWithAccount(ctx, &auth.Account{ + ID: "123", + Type: "user", + Issuer: "micro", + Metadata: map[string]string{}, + Scopes: []string{"space"}, + Name: "john@example.com", + }) + rsp := pb.DeleteResponse{} + err := handler.Delete(ctx, &pb.DeleteRequest{ + Name: tc.objName, + }, &rsp) + if tc.err != nil { + g.Expect(err).To(Equal(tc.err)) + } else { + g.Expect(err).To(BeNil()) + } + + }) + } + +} + +func TestList(t *testing.T) { + g := NewWithT(t) + tcs := []struct { + name string + prefix string + err error + list func(input *sthree.ListObjectsInput) (*sthree.ListObjectsInput, error) + }{ + { + name: "Simple case", + prefix: "foo", + }, + { + name: "Empty prefix", + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + handler := Space{ + conf: conf{ + AccessKey: "access", + SecretKey: "secret", + Endpoint: "example.com", + SpaceName: "my-space", + SSL: true, + Region: "ams3", + BaseURL: "https://my-space.ams3.example.com", + }, + client: &mockS3Client{ + list: func(input *sthree.ListObjectsInput) (*sthree.ListObjectsOutput, error) { + g.Expect(input.Bucket).To(Equal(aws.String("my-space"))) + g.Expect(input.Prefix).To(Equal(aws.String("micro/123/" + tc.prefix))) + return &sthree.ListObjectsOutput{}, nil + }}, + } + ctx := context.Background() + ctx = auth.ContextWithAccount(ctx, &auth.Account{ + ID: "123", + Type: "user", + Issuer: "micro", + Metadata: map[string]string{}, + Scopes: []string{"space"}, + Name: "john@example.com", + }) + rsp := pb.ListResponse{} + err := handler.List(ctx, &pb.ListRequest{ + Prefix: tc.prefix, + }, &rsp) + if tc.err != nil { + g.Expect(err).To(Equal(tc.err)) + } else { + g.Expect(err).To(BeNil()) + } + + }) + } + +} + +func TestHead(t *testing.T) { + g := NewWithT(t) + tcs := []struct { + name string + objectName string + url string + visibility string + modified int64 + created int64 + err error + head func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) + }{ + { + name: "Simple case", + objectName: "foo.jpg", + visibility: "public", + url: "https://my-space.ams3.example.com/micro/123/foo.jpg", + created: 1638547905, + modified: 1638547906, + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + g.Expect(*input.Bucket).To(Equal("my-space")) + g.Expect(*input.Key).To(Equal("micro/123/foo.jpg")) + + return &sthree.HeadObjectOutput{ + LastModified: aws.Time(time.Unix(1638547906, 0)), + Metadata: map[string]*string{ + mdCreated: aws.String("1638547905"), + mdVisibility: aws.String(visibilityPublic), + }, + }, nil + }, + }, + { + name: "Empty prefix", + err: errors.BadRequest("space.Head", "Missing name param"), + }, + { + name: "Not found", + objectName: "foo.jpg", + err: errors.BadRequest("space.Head", "Object not found"), + head: func(input *sthree.HeadObjectInput) (*sthree.HeadObjectOutput, error) { + return nil, mockError{code: "NotFound"} + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + handler := Space{ + conf: conf{ + AccessKey: "access", + SecretKey: "secret", + Endpoint: "example.com", + SpaceName: "my-space", + SSL: true, + Region: "ams3", + BaseURL: "https://my-space.ams3.example.com", + }, + client: &mockS3Client{ + head: tc.head, + }, + } + ctx := context.Background() + ctx = auth.ContextWithAccount(ctx, &auth.Account{ + ID: "123", + Type: "user", + Issuer: "micro", + Metadata: map[string]string{}, + Scopes: []string{"space"}, + Name: "john@example.com", + }) + rsp := pb.HeadResponse{} + err := handler.Head(ctx, &pb.HeadRequest{ + Name: tc.objectName, + }, &rsp) + if tc.err != nil { + g.Expect(err).To(Equal(tc.err)) + } else { + g.Expect(err).To(BeNil()) + g.Expect(rsp.Object.Name).To(Equal(tc.objectName)) + g.Expect(rsp.Object.Url).To(Equal("https://my-space.ams3.example.com/micro/123/" + tc.objectName)) + g.Expect(rsp.Object.Visibility).To(Equal(tc.visibility)) + g.Expect(rsp.Object.Created).To(Equal(tc.created)) + g.Expect(rsp.Object.Modified).To(Equal(tc.modified)) + } + + }) + } + +} diff --git a/space/main.go b/space/main.go index 2676a42..7a71f33 100644 --- a/space/main.go +++ b/space/main.go @@ -1,10 +1,10 @@ package main import ( - "github.com/micro/services/space/handler" - pb "github.com/micro/services/space/proto" "github.com/micro/micro/v3/service" + "github.com/micro/micro/v3/service/api" "github.com/micro/micro/v3/service/logger" + "github.com/micro/services/space/handler" ) func main() { @@ -15,7 +15,19 @@ func main() { ) // Register handler - pb.RegisterSpaceHandler(srv.Server(), new(handler.Space)) + //pb.RegisterSpaceHandler(srv.Server(), handler.NewSpace(srv)) + + srv.Server().Handle( + srv.Server().NewHandler( + handler.NewSpace(srv), + api.WithEndpoint( + &api.Endpoint{ + Name: "Space.Read", + Handler: "api", + Method: []string{"POST", "GET"}, + Path: []string{"/space/read"}, + }), + )) // Run service if err := srv.Run(); err != nil { diff --git a/space/proto/space.pb.go b/space/proto/space.pb.go index 43bd342..2c7b0ec 100644 --- a/space/proto/space.pb.go +++ b/space/proto/space.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.15.6 +// protoc-gen-go v1.26.0 +// protoc v3.15.5 // source: proto/space.proto package space @@ -20,18 +20,24 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// Vote to have the Space api launched faster! -type VoteRequest struct { +// Create an object. Returns error if object with this name already exists. If you want to update an existing object use the `Update` endpoint +// You need to send the request as a multipart/form-data rather than the usual application/json +// with each parameter as a form field. +type CreateRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // optional message - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + // The contents of the object + Object []byte `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` + // The name of the object. Use forward slash delimiter to implement a nested directory-like structure e.g. images/foo.jpg + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Who can see this object? "public" or "private", defaults to "private" + Visibility string `protobuf:"bytes,3,opt,name=visibility,proto3" json:"visibility,omitempty"` } -func (x *VoteRequest) Reset() { - *x = VoteRequest{} +func (x *CreateRequest) Reset() { + *x = CreateRequest{} if protoimpl.UnsafeEnabled { mi := &file_proto_space_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -39,13 +45,13 @@ func (x *VoteRequest) Reset() { } } -func (x *VoteRequest) String() string { +func (x *CreateRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*VoteRequest) ProtoMessage() {} +func (*CreateRequest) ProtoMessage() {} -func (x *VoteRequest) ProtoReflect() protoreflect.Message { +func (x *CreateRequest) ProtoReflect() protoreflect.Message { mi := &file_proto_space_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -57,29 +63,43 @@ func (x *VoteRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use VoteRequest.ProtoReflect.Descriptor instead. -func (*VoteRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. +func (*CreateRequest) Descriptor() ([]byte, []int) { return file_proto_space_proto_rawDescGZIP(), []int{0} } -func (x *VoteRequest) GetMessage() string { +func (x *CreateRequest) GetObject() []byte { if x != nil { - return x.Message + return x.Object + } + return nil +} + +func (x *CreateRequest) GetName() string { + if x != nil { + return x.Name } return "" } -type VoteResponse struct { +func (x *CreateRequest) GetVisibility() string { + if x != nil { + return x.Visibility + } + return "" +} + +type CreateResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // response message - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + // A public URL to access the object if visibility is "public" + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` } -func (x *VoteResponse) Reset() { - *x = VoteResponse{} +func (x *CreateResponse) Reset() { + *x = CreateResponse{} if protoimpl.UnsafeEnabled { mi := &file_proto_space_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -87,13 +107,13 @@ func (x *VoteResponse) Reset() { } } -func (x *VoteResponse) String() string { +func (x *CreateResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*VoteResponse) ProtoMessage() {} +func (*CreateResponse) ProtoMessage() {} -func (x *VoteResponse) ProtoReflect() protoreflect.Message { +func (x *CreateResponse) ProtoReflect() protoreflect.Message { mi := &file_proto_space_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -105,34 +125,723 @@ func (x *VoteResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use VoteResponse.ProtoReflect.Descriptor instead. -func (*VoteResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateResponse.ProtoReflect.Descriptor instead. +func (*CreateResponse) Descriptor() ([]byte, []int) { return file_proto_space_proto_rawDescGZIP(), []int{1} } -func (x *VoteResponse) GetMessage() string { +func (x *CreateResponse) GetUrl() string { if x != nil { - return x.Message + return x.Url } return "" } +// Update an object. If an object with this name does not exist, creates a new one. +// You need to send the request as a multipart/form-data rather than the usual application/json +// with each parameter as a form field. +type UpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The contents of the object + Object []byte `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` + // The name of the object. Use forward slash delimiter to implement a nested directory-like structure e.g. images/foo.jpg + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Who can see this object? "public" or "private", defaults to "private" + Visibility string `protobuf:"bytes,3,opt,name=visibility,proto3" json:"visibility,omitempty"` +} + +func (x *UpdateRequest) Reset() { + *x = UpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequest) ProtoMessage() {} + +func (x *UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{2} +} + +func (x *UpdateRequest) GetObject() []byte { + if x != nil { + return x.Object + } + return nil +} + +func (x *UpdateRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateRequest) GetVisibility() string { + if x != nil { + return x.Visibility + } + return "" +} + +type UpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // A public URL to access the object if visibility is "public" + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *UpdateResponse) Reset() { + *x = UpdateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateResponse) ProtoMessage() {} + +func (x *UpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateResponse.ProtoReflect.Descriptor instead. +func (*UpdateResponse) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{3} +} + +func (x *UpdateResponse) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +// Delete an object +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The name of the object. Use forward slash delimiter to implement a nested directory-like structure e.g. images/foo.jpg + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{4} +} + +func (x *DeleteRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteResponse) Reset() { + *x = DeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResponse) ProtoMessage() {} + +func (x *DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{5} +} + +// List the objects in the space +type ListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // optional prefix for the name e.g. to return all the objects in the images directory pass images/ + Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{6} +} + +func (x *ListRequest) GetPrefix() string { + if x != nil { + return x.Prefix + } + return "" +} + +type ListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Objects []*ListObject `protobuf:"bytes,1,rep,name=objects,proto3" json:"objects,omitempty"` +} + +func (x *ListResponse) Reset() { + *x = ListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListResponse) ProtoMessage() {} + +func (x *ListResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. +func (*ListResponse) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{7} +} + +func (x *ListResponse) GetObjects() []*ListObject { + if x != nil { + return x.Objects + } + return nil +} + +type ListObject struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // when was this last modified + Modified int64 `protobuf:"varint,2,opt,name=modified,proto3" json:"modified,omitempty"` + Url string `protobuf:"bytes,3,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *ListObject) Reset() { + *x = ListObject{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListObject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListObject) ProtoMessage() {} + +func (x *ListObject) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListObject.ProtoReflect.Descriptor instead. +func (*ListObject) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{8} +} + +func (x *ListObject) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ListObject) GetModified() int64 { + if x != nil { + return x.Modified + } + return 0 +} + +func (x *ListObject) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +// Retrieve meta information about an object +type HeadRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *HeadRequest) Reset() { + *x = HeadRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeadRequest) ProtoMessage() {} + +func (x *HeadRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeadRequest.ProtoReflect.Descriptor instead. +func (*HeadRequest) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{9} +} + +func (x *HeadRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type HeadResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Object *HeadObject `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` +} + +func (x *HeadResponse) Reset() { + *x = HeadResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeadResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeadResponse) ProtoMessage() {} + +func (x *HeadResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeadResponse.ProtoReflect.Descriptor instead. +func (*HeadResponse) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{10} +} + +func (x *HeadResponse) GetObject() *HeadObject { + if x != nil { + return x.Object + } + return nil +} + +type HeadObject struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // when was this last modified + Modified int64 `protobuf:"varint,2,opt,name=modified,proto3" json:"modified,omitempty"` + // when was this created + Created int64 `protobuf:"varint,3,opt,name=created,proto3" json:"created,omitempty"` + // is this public or private + Visibility string `protobuf:"bytes,4,opt,name=visibility,proto3" json:"visibility,omitempty"` + // URL to access the object if it is public + Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *HeadObject) Reset() { + *x = HeadObject{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeadObject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeadObject) ProtoMessage() {} + +func (x *HeadObject) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeadObject.ProtoReflect.Descriptor instead. +func (*HeadObject) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{11} +} + +func (x *HeadObject) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HeadObject) GetModified() int64 { + if x != nil { + return x.Modified + } + return 0 +} + +func (x *HeadObject) GetCreated() int64 { + if x != nil { + return x.Created + } + return 0 +} + +func (x *HeadObject) GetVisibility() string { + if x != nil { + return x.Visibility + } + return "" +} + +func (x *HeadObject) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +// Read/download the object +type ReadRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ReadRequest) Reset() { + *x = ReadRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadRequest) ProtoMessage() {} + +func (x *ReadRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadRequest.ProtoReflect.Descriptor instead. +func (*ReadRequest) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{12} +} + +func (x *ReadRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Returns the raw object +type ReadResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ReadResponse) Reset() { + *x = ReadResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_space_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReadResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadResponse) ProtoMessage() {} + +func (x *ReadResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_space_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadResponse.ProtoReflect.Descriptor instead. +func (*ReadResponse) Descriptor() ([]byte, []int) { + return file_proto_space_proto_rawDescGZIP(), []int{13} +} + var File_proto_space_proto protoreflect.FileDescriptor var file_proto_space_proto_rawDesc = []byte{ 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x56, 0x6f, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x28, 0x0a, 0x0c, 0x56, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x3a, 0x0a, - 0x05, 0x53, 0x70, 0x61, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x56, 0x6f, 0x74, 0x65, 0x12, 0x12, - 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x56, 0x6f, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x5b, 0x0a, 0x0d, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x69, 0x73, + 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x22, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x5b, 0x0a, 0x0d, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x69, + 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x22, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x23, 0x0a, 0x0d, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x10, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x25, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x3b, 0x0a, 0x0c, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x07, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x07, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x4e, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x6f, 0x64, + 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x6f, 0x64, + 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x21, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x39, 0x0a, 0x0c, 0x48, 0x65, + 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x06, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x88, 0x01, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x6f, 0x64, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x6f, 0x64, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1e, + 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x22, 0x21, 0x0a, 0x0b, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xcb, 0x02, 0x0a, 0x05, 0x53, 0x70, 0x61, 0x63, 0x65, 0x12, 0x37, 0x0a, + 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x12, 0x14, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x15, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, + 0x12, 0x12, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04, 0x48, + 0x65, 0x61, 0x64, 0x12, 0x12, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, + 0x48, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, + 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x12, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -147,19 +856,43 @@ func file_proto_space_proto_rawDescGZIP() []byte { return file_proto_space_proto_rawDescData } -var file_proto_space_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_space_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_proto_space_proto_goTypes = []interface{}{ - (*VoteRequest)(nil), // 0: space.VoteRequest - (*VoteResponse)(nil), // 1: space.VoteResponse + (*CreateRequest)(nil), // 0: space.CreateRequest + (*CreateResponse)(nil), // 1: space.CreateResponse + (*UpdateRequest)(nil), // 2: space.UpdateRequest + (*UpdateResponse)(nil), // 3: space.UpdateResponse + (*DeleteRequest)(nil), // 4: space.DeleteRequest + (*DeleteResponse)(nil), // 5: space.DeleteResponse + (*ListRequest)(nil), // 6: space.ListRequest + (*ListResponse)(nil), // 7: space.ListResponse + (*ListObject)(nil), // 8: space.ListObject + (*HeadRequest)(nil), // 9: space.HeadRequest + (*HeadResponse)(nil), // 10: space.HeadResponse + (*HeadObject)(nil), // 11: space.HeadObject + (*ReadRequest)(nil), // 12: space.ReadRequest + (*ReadResponse)(nil), // 13: space.ReadResponse } var file_proto_space_proto_depIdxs = []int32{ - 0, // 0: space.Space.Vote:input_type -> space.VoteRequest - 1, // 1: space.Space.Vote:output_type -> space.VoteResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 8, // 0: space.ListResponse.objects:type_name -> space.ListObject + 11, // 1: space.HeadResponse.object:type_name -> space.HeadObject + 0, // 2: space.Space.Create:input_type -> space.CreateRequest + 2, // 3: space.Space.Update:input_type -> space.UpdateRequest + 4, // 4: space.Space.Delete:input_type -> space.DeleteRequest + 6, // 5: space.Space.List:input_type -> space.ListRequest + 9, // 6: space.Space.Head:input_type -> space.HeadRequest + 12, // 7: space.Space.Read:input_type -> space.ReadRequest + 1, // 8: space.Space.Create:output_type -> space.CreateResponse + 3, // 9: space.Space.Update:output_type -> space.UpdateResponse + 5, // 10: space.Space.Delete:output_type -> space.DeleteResponse + 7, // 11: space.Space.List:output_type -> space.ListResponse + 10, // 12: space.Space.Head:output_type -> space.HeadResponse + 13, // 13: space.Space.Read:output_type -> space.ReadResponse + 8, // [8:14] is the sub-list for method output_type + 2, // [2:8] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_proto_space_proto_init() } @@ -169,7 +902,7 @@ func file_proto_space_proto_init() { } if !protoimpl.UnsafeEnabled { file_proto_space_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VoteRequest); i { + switch v := v.(*CreateRequest); i { case 0: return &v.state case 1: @@ -181,7 +914,151 @@ func file_proto_space_proto_init() { } } file_proto_space_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VoteResponse); i { + switch v := v.(*CreateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListObject); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeadRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeadResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeadObject); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_space_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReadResponse); i { case 0: return &v.state case 1: @@ -199,7 +1076,7 @@ func file_proto_space_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_space_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 14, NumExtensions: 0, NumServices: 1, }, diff --git a/space/proto/space.pb.micro.go b/space/proto/space.pb.micro.go index f5e6ba8..a210ada 100644 --- a/space/proto/space.pb.micro.go +++ b/space/proto/space.pb.micro.go @@ -42,7 +42,12 @@ func NewSpaceEndpoints() []*api.Endpoint { // Client API for Space service type SpaceService interface { - Vote(ctx context.Context, in *VoteRequest, opts ...client.CallOption) (*VoteResponse, error) + Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) + Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) + Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) + List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) + Head(ctx context.Context, in *HeadRequest, opts ...client.CallOption) (*HeadResponse, error) + Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) } type spaceService struct { @@ -57,9 +62,59 @@ func NewSpaceService(name string, c client.Client) SpaceService { } } -func (c *spaceService) Vote(ctx context.Context, in *VoteRequest, opts ...client.CallOption) (*VoteResponse, error) { - req := c.c.NewRequest(c.name, "Space.Vote", in) - out := new(VoteResponse) +func (c *spaceService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) { + req := c.c.NewRequest(c.name, "Space.Create", in) + out := new(CreateResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *spaceService) Update(ctx context.Context, in *UpdateRequest, opts ...client.CallOption) (*UpdateResponse, error) { + req := c.c.NewRequest(c.name, "Space.Update", in) + out := new(UpdateResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *spaceService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) { + req := c.c.NewRequest(c.name, "Space.Delete", in) + out := new(DeleteResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *spaceService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) { + req := c.c.NewRequest(c.name, "Space.List", in) + out := new(ListResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *spaceService) Head(ctx context.Context, in *HeadRequest, opts ...client.CallOption) (*HeadResponse, error) { + req := c.c.NewRequest(c.name, "Space.Head", in) + out := new(HeadResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *spaceService) Read(ctx context.Context, in *ReadRequest, opts ...client.CallOption) (*ReadResponse, error) { + req := c.c.NewRequest(c.name, "Space.Read", in) + out := new(ReadResponse) err := c.c.Call(ctx, req, out, opts...) if err != nil { return nil, err @@ -70,12 +125,22 @@ func (c *spaceService) Vote(ctx context.Context, in *VoteRequest, opts ...client // Server API for Space service type SpaceHandler interface { - Vote(context.Context, *VoteRequest, *VoteResponse) error + Create(context.Context, *CreateRequest, *CreateResponse) error + Update(context.Context, *UpdateRequest, *UpdateResponse) error + Delete(context.Context, *DeleteRequest, *DeleteResponse) error + List(context.Context, *ListRequest, *ListResponse) error + Head(context.Context, *HeadRequest, *HeadResponse) error + Read(context.Context, *ReadRequest, *ReadResponse) error } func RegisterSpaceHandler(s server.Server, hdlr SpaceHandler, opts ...server.HandlerOption) error { type space interface { - Vote(ctx context.Context, in *VoteRequest, out *VoteResponse) error + Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error + Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error + Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error + List(ctx context.Context, in *ListRequest, out *ListResponse) error + Head(ctx context.Context, in *HeadRequest, out *HeadResponse) error + Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error } type Space struct { space @@ -88,6 +153,26 @@ type spaceHandler struct { SpaceHandler } -func (h *spaceHandler) Vote(ctx context.Context, in *VoteRequest, out *VoteResponse) error { - return h.SpaceHandler.Vote(ctx, in, out) +func (h *spaceHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error { + return h.SpaceHandler.Create(ctx, in, out) +} + +func (h *spaceHandler) Update(ctx context.Context, in *UpdateRequest, out *UpdateResponse) error { + return h.SpaceHandler.Update(ctx, in, out) +} + +func (h *spaceHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error { + return h.SpaceHandler.Delete(ctx, in, out) +} + +func (h *spaceHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error { + return h.SpaceHandler.List(ctx, in, out) +} + +func (h *spaceHandler) Head(ctx context.Context, in *HeadRequest, out *HeadResponse) error { + return h.SpaceHandler.Head(ctx, in, out) +} + +func (h *spaceHandler) Read(ctx context.Context, in *ReadRequest, out *ReadResponse) error { + return h.SpaceHandler.Read(ctx, in, out) } diff --git a/space/proto/space.proto b/space/proto/space.proto index b03dcd0..ca5893e 100644 --- a/space/proto/space.proto +++ b/space/proto/space.proto @@ -5,16 +5,100 @@ package space; option go_package = "./proto;space"; service Space { - rpc Vote(VoteRequest) returns (VoteResponse) {} + rpc Create(CreateRequest) returns (CreateResponse) {} + rpc Update(UpdateRequest) returns (UpdateResponse) {} + rpc Delete(DeleteRequest) returns (DeleteResponse) {} + rpc List(ListRequest) returns (ListResponse) {} + rpc Head(HeadRequest) returns (HeadResponse) {} + rpc Read(ReadRequest) returns (ReadResponse) {} } -// Vote to have the Space api launched faster! -message VoteRequest { - // optional message - string message = 1; +// Create an object. Returns error if object with this name already exists. If you want to update an existing object use the `Update` endpoint +// You need to send the request as a multipart/form-data rather than the usual application/json +// with each parameter as a form field. +message CreateRequest { + // The contents of the object + bytes object = 1; + // The name of the object. Use forward slash delimiter to implement a nested directory-like structure e.g. images/foo.jpg + string name = 2; + // Who can see this object? "public" or "private", defaults to "private" + string visibility = 3; } -message VoteResponse { - // response message - string message = 2; +message CreateResponse { + // A public URL to access the object if visibility is "public" + string url = 1; +} + +// Update an object. If an object with this name does not exist, creates a new one. +// You need to send the request as a multipart/form-data rather than the usual application/json +// with each parameter as a form field. +message UpdateRequest { + // The contents of the object + bytes object = 1; + // The name of the object. Use forward slash delimiter to implement a nested directory-like structure e.g. images/foo.jpg + string name = 2; + // Who can see this object? "public" or "private", defaults to "private" + string visibility = 3; +} + +message UpdateResponse { + // A public URL to access the object if visibility is "public" + string url = 1; +} + +// Delete an object +message DeleteRequest { + // The name of the object. Use forward slash delimiter to implement a nested directory-like structure e.g. images/foo.jpg + string name = 1; +} + +message DeleteResponse {} + +// List the objects in the space +message ListRequest { + // optional prefix for the name e.g. to return all the objects in the images directory pass images/ + string prefix = 1; +} + +message ListResponse { + repeated ListObject objects = 1; +} + +message ListObject { + string name = 1; + // when was this last modified + int64 modified = 2; + string url = 3; +} + +// Retrieve meta information about an object +message HeadRequest { + string name = 1; +} + +message HeadResponse { + HeadObject object = 1; +} + +message HeadObject { + string name = 1; + // when was this last modified + int64 modified = 2; + // when was this created + int64 created = 3; + // is this public or private + string visibility = 4; + // URL to access the object if it is public + string url = 5; +} + +// Read/download the object +message ReadRequest { + string name = 1; +} + +// Returns the raw object +message ReadResponse { + } diff --git a/space/publicapi.json b/space/publicapi.json index a42e551..53b2161 100644 --- a/space/publicapi.json +++ b/space/publicapi.json @@ -1,6 +1,6 @@ { "name": "space", "icon": "🖴", - "category": "coming soon", - "display_name": "Space (Coming Soon)" + "category": "storage", + "display_name": "Space" }