mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
Space API (#290)
This commit is contained in:
67
email/handler/email_test.go
Normal file
67
email/handler/email_test.go
Normal 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
5
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
|
||||
|
||||
26
go.sum
26
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=
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,13 +1,90 @@
|
||||
{
|
||||
"vote": [
|
||||
"create": [
|
||||
{
|
||||
"title": "Vote for the API",
|
||||
"title": "Create an object",
|
||||
"run_check": false,
|
||||
"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": {
|
||||
"message": "Thanks for the vote!"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
534
space/handler/space_test.go
Normal file
534
space/handler/space_test.go
Normal 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))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "space",
|
||||
"icon": "🖴",
|
||||
"category": "coming soon",
|
||||
"display_name": "Space (Coming Soon)"
|
||||
"category": "storage",
|
||||
"display_name": "Space"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user