Space API (#290)

This commit is contained in:
Dominic Wong
2021-12-09 10:56:42 +00:00
committed by GitHub
parent bed010d0a9
commit c61badab4b
13 changed files with 2126 additions and 109 deletions

View File

@@ -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))
})
}
}

5
go.mod
View File

@@ -7,6 +7,7 @@ require (
github.com/SlyMarbo/rss v1.0.1 github.com/SlyMarbo/rss v1.0.1
github.com/Teamwork/spamc v0.0.0-20200109085853-a4e0c5c3f7a0 github.com/Teamwork/spamc v0.0.0-20200109085853-a4e0c5c3f7a0
github.com/asim/mq v0.1.0 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/goml v0.0.0-20190412180403-e1f51f713598 // indirect
github.com/cdipaolo/sentiment v0.0.0-20200617002423-c697f64e7f10 github.com/cdipaolo/sentiment v0.0.0-20200617002423-c697f64e7f10
github.com/crufter/lexer v0.0.0-20120907053443-23fe8c7add01 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/mattheath/kala v0.0.0-20171219141654-d6276794bf0e
github.com/micro/micro/v3 v3.8.0 github.com/micro/micro/v3 v3.8.0
github.com/miekg/dns v1.1.31 // indirect 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/oschwald/geoip2-golang v1.5.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/paulmach/go.geo v0.0.0-20180829195134-22b514266d33 github.com/paulmach/go.geo v0.0.0-20180829195134-22b514266d33
@@ -50,7 +53,7 @@ require (
github.com/ttacon/libphonenumber v1.2.1 // indirect github.com/ttacon/libphonenumber v1.2.1 // indirect
go.mongodb.org/mongo-driver v1.7.2 go.mongodb.org/mongo-driver v1.7.2
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 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 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1
google.golang.org/api v0.59.0 google.golang.org/api v0.59.0
google.golang.org/grpc/examples v0.0.0-20211103202053-3b94303f3754 // indirect google.golang.org/grpc/examples v0.0.0-20211103202053-3b94303f3754 // indirect

26
go.sum
View File

@@ -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 h1:v/gg/blpqXDStNYveq6bGEhT1DQrH1Iaxd5srMjszX0=
github.com/asim/mq v0.1.0/go.mod h1:gSmd9l3K8i9qM440G4YXwc2Zjn9Y77+AImRc3GmjK78= 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.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 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= 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= 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 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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.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/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.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/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.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.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/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= 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 h1:PSsFm2SRpq9LnaRHLz4u9ZZ3liWjgXM6OMxXE4/qlgY=
github.com/kevinburke/twilio-go v0.0.0-20210327194925-1623146bcf73/go.mod h1:Fm9alkN1/LPVY1eqD/psyMwPWE4VWl4P01/nTYZKzBk= 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/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.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.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= 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= 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-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/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/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 h1:RTH2835RJ4/aqLZGMjGCIf7HroCmYlJh2KRHHuSL/AE=
github.com/micro/micro/v3 v3.8.0/go.mod h1:gjFa8T2ouD6BvorPTVPXLWtrRJwSKT5KxUNuu23Kkts= 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.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 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 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 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-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/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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.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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= 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/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/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 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/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/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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-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-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-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 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 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= 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-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-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-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-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-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-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-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-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-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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/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-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-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-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-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-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-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 h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/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/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.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.44.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/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.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View File

@@ -1,5 +1,7 @@
GOPATH:=$(shell go env GOPATH) GOPATH:=$(shell go env GOPATH)
MODIFY=Mproto/imports/api.proto=github.com/micro/micro/v3/proto/api
.PHONY: init .PHONY: init
init: init:
go get -u github.com/golang/protobuf/proto go get -u github.com/golang/protobuf/proto
@@ -13,7 +15,7 @@ api:
.PHONY: proto .PHONY: proto
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 .PHONY: build
build: build:

View File

@@ -3,5 +3,7 @@ Infinite cloud storage
# Space Service # Space Service
Space for simple object storage. Put anything in the cloud 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.

View File

@@ -1,13 +1,90 @@
{ {
"vote": [ "create": [
{ {
"title": "Vote for the API", "title": "Create an object",
"run_check": false, "run_check": false,
"request": { "request": {
"message": "Launch it!" "object": "<file bytes>",
"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": "<file bytes>",
"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": { "response": {
"message": "Thanks for the vote!"
} }
} }
] ]

View File

@@ -1,50 +1,306 @@
package handler package handler
import ( import (
"bytes"
"context" "context"
"sync" "encoding/json"
"fmt"
"strconv"
"strings"
"time" "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/services/pkg/tenant"
"github.com/micro/micro/v3/service/store"
pb "github.com/micro/services/space/proto" 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 ( visibilityPrivate = "private"
mtx sync.RWMutex visibilityPublic = "public"
voteKey = "votes/"
) )
type Vote struct { type Space struct {
Id string `json:"id"` conf conf
Message string `json:"message"` client s3iface.S3API
VotedAt time.Time `json:"voted_at"`
} }
func (n *Space) Vote(ctx context.Context, req *pb.VoteRequest, rsp *pb.VoteResponse) error { type conf struct {
mtx.Lock() AccessKey string `json:"access_key"`
defer mtx.Unlock() 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) func NewSpace(srv *service.Service) *Space {
if !ok { var c conf
id = "micro" 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{ sess := session.Must(session.NewSession(&aws.Config{
Id: id, Endpoint: &c.Endpoint,
Message: req.Message, Region: &c.Region,
VotedAt: time.Now(), 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 if create && exists {
store.Write(rec) 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 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
}

534
space/handler/space_test.go Normal file
View File

@@ -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))
}
})
}
}

View File

@@ -1,10 +1,10 @@
package main package main
import ( 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"
"github.com/micro/micro/v3/service/api"
"github.com/micro/micro/v3/service/logger" "github.com/micro/micro/v3/service/logger"
"github.com/micro/services/space/handler"
) )
func main() { func main() {
@@ -15,7 +15,19 @@ func main() {
) )
// Register handler // 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 // Run service
if err := srv.Run(); err != nil { if err := srv.Run(); err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,12 @@ func NewSpaceEndpoints() []*api.Endpoint {
// Client API for Space service // Client API for Space service
type SpaceService interface { 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 { 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) { func (c *spaceService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
req := c.c.NewRequest(c.name, "Space.Vote", in) req := c.c.NewRequest(c.name, "Space.Create", in)
out := new(VoteResponse) 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...) err := c.c.Call(ctx, req, out, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -70,12 +125,22 @@ func (c *spaceService) Vote(ctx context.Context, in *VoteRequest, opts ...client
// Server API for Space service // Server API for Space service
type SpaceHandler interface { 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 { func RegisterSpaceHandler(s server.Server, hdlr SpaceHandler, opts ...server.HandlerOption) error {
type space interface { 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 { type Space struct {
space space
@@ -88,6 +153,26 @@ type spaceHandler struct {
SpaceHandler SpaceHandler
} }
func (h *spaceHandler) Vote(ctx context.Context, in *VoteRequest, out *VoteResponse) error { func (h *spaceHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
return h.SpaceHandler.Vote(ctx, in, out) 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)
} }

View File

@@ -5,16 +5,100 @@ package space;
option go_package = "./proto;space"; option go_package = "./proto;space";
service 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! // Create an object. Returns error if object with this name already exists. If you want to update an existing object use the `Update` endpoint
message VoteRequest { // You need to send the request as a multipart/form-data rather than the usual application/json
// optional message // with each parameter as a form field.
string message = 1; 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 { message CreateResponse {
// response message // A public URL to access the object if visibility is "public"
string message = 2; 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 {
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "space", "name": "space",
"icon": "🖴", "icon": "🖴",
"category": "coming soon", "category": "storage",
"display_name": "Space (Coming Soon)" "display_name": "Space"
} }