initial commit

This commit is contained in:
Djeeberjr 2021-07-26 14:52:36 +02:00
commit 48eea73833
9 changed files with 690 additions and 0 deletions

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Usefull links
[Graphql GH](https://github.com/graphql-go/graphql)
[Graphql Handler GH](https://github.com/graphql-go/handler)
[Dataloader GH](https://github.com/graph-gophers/dataloader)

13
cmd/s3Browser.go Normal file
View File

@ -0,0 +1,13 @@
package main
import s3browser "git.kapelle.org/niklas/s3browser/internal"
func main() {
s3browser.Start(s3browser.AppConfig{
S3Endoint: "localhost:9000",
S3SSL: false,
S3AccessKey: "testo",
S3SecretKey: "testotesto",
S3Buket: "dev",
})
}

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
version: '2'
services:
minio:
container_name: minio_dev
image: minio/minio
environment:
- MINIO_ROOT_USER=admin
- MINIO_ROOT_PASSWORD=hunter22
ports:
- 9000:9000
- 9001:9001
command: server /data --console-address ":9001"
volumes:
- minio_dev:/data
volumes:
minio_dev:
name: minio_dev

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module git.kapelle.org/niklas/s3browser
go 1.16
require (
github.com/graph-gophers/dataloader v5.0.0+incompatible
github.com/graphql-go/graphql v0.7.9
github.com/graphql-go/handler v0.2.3
github.com/minio/minio-go/v7 v7.0.12
github.com/opentracing/opentracing-go v1.2.0 // indirect
)

84
go.sum Normal file
View File

@ -0,0 +1,84 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug=
github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4=
github.com/graphql-go/graphql v0.7.9 h1:5Va/Rt4l5g3YjwDnid3vFfn43faaQBq7rMcIZ0VnV34=
github.com/graphql-go/graphql v0.7.9/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI=
github.com/graphql-go/handler v0.2.3 h1:CANh8WPnl5M9uA25c2GBhPqJhE53Fg0Iue/fRNla71E=
github.com/graphql-go/handler v0.2.3/go.mod h1:leLF6RpV5uZMN1CdImAxuiayrYYhOk33bZciaUGaXeU=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio-go/v7 v7.0.12 h1:/4pxUdwn9w0QEryNkrrWaodIESPRX+NxpO0Q6hVdaAA=
github.com/minio/minio-go/v7 v7.0.12/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

200
internal/dataloader.go Normal file
View File

@ -0,0 +1,200 @@
package s3browser
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/graph-gophers/dataloader"
"github.com/minio/minio-go/v7"
)
// listObjectsBatch batch func for calling s3.ListObjects()
func listObjectsBatch(c context.Context, k dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
s3Client, ok := c.Value("s3Client").(*minio.Client)
if !ok {
return handleLoaderError(k, fmt.Errorf("Failed to get s3Client from context"))
}
for _, v := range k {
results = append(results, &dataloader.Result{
Data: listObjects(s3Client, bucketName, v.String(), false),
})
}
return results
}
// listObjects helper func for listObjectsBatch
func listObjects(s3Client *minio.Client, bukitName, path string, recursive bool) []minio.ObjectInfo {
objectCh := s3Client.ListObjects(context.Background(), bukitName, minio.ListObjectsOptions{
Prefix: path,
Recursive: false,
})
result := make([]minio.ObjectInfo, 0)
for obj := range objectCh {
result = append(result, obj)
}
return result
}
// getFilesBatch batch func for getting all files in path. Uses "listObjects" dataloader
func getFilesBatch(c context.Context, k dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
loader, ok := c.Value("loader").(map[string]*dataloader.Loader)
if !ok {
return handleLoaderError(k, fmt.Errorf("Failed to get loader from context"))
}
for _, v := range k {
path := v.String()
files := make([]File, 0)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
thunk := loader["listObjects"].Load(c, dataloader.StringKey(path))
objects, _ := thunk()
// TODO: handle thunk error
for _, obj := range objects.([]minio.ObjectInfo) {
if obj.Err != nil {
// TODO: how to handle?
} else {
if !strings.HasSuffix(obj.Key, "/") {
files = append(files, File{
ID: obj.Key,
Name: filepath.Base(obj.Key),
Size: obj.Size,
ContentType: obj.ContentType,
ETag: obj.ETag,
})
}
}
}
results = append(results, &dataloader.Result{
Data: files,
Error: nil,
})
}
return results
}
// getFileBatch batch func for getting object info
func getFileBatch(c context.Context, k dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
s3Client, ok := c.Value("s3Client").(*minio.Client)
if !ok {
return handleLoaderError(k, fmt.Errorf("Failed to get s3Client from context"))
}
for _, v := range k {
obj, err := s3Client.StatObject(context.Background(), bucketName, v.String(), minio.StatObjectOptions{})
if err != nil {
results = append(results, &dataloader.Result{
Data: nil,
Error: err,
})
} else {
results = append(results, &dataloader.Result{
Data: &File{
ID: obj.Key,
Size: obj.Size,
ContentType: obj.ContentType,
ETag: obj.ETag,
},
Error: nil,
})
}
}
return results
}
// getDirsBatch batch func for getting dirs in a path
func getDirsBatch(c context.Context, k dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
loader, ok := c.Value("loader").(map[string]*dataloader.Loader)
if !ok {
return handleLoaderError(k, fmt.Errorf("Failed to get loader from context"))
}
for _, v := range k {
path := v.String()
dirs := make([]Directory, 0)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
thunk := loader["listObjects"].Load(c, dataloader.StringKey(path))
objects, _ := thunk()
// TODO: handle thunk error
for _, obj := range objects.([]minio.ObjectInfo) {
if obj.Err != nil {
// TODO: how to handle?
} else {
if strings.HasSuffix(obj.Key, "/") {
dirs = append(dirs, Directory{
ID: obj.Key,
Name: filepath.Base(obj.Key),
})
}
}
}
results = append(results, &dataloader.Result{
Data: dirs,
Error: nil,
})
}
return results
}
// handleLoaderError helper func when the whole batch failed
func handleLoaderError(k dataloader.Keys, err error) []*dataloader.Result {
var results []*dataloader.Result
for range k {
results = append(results, &dataloader.Result{
Data: nil,
Error: err,
})
}
return results
}
// createDataloader create all dataloaders and return a map of them
func createDataloader() map[string]*dataloader.Loader {
loaderMap := make(map[string]*dataloader.Loader, 0)
loaderMap["getFiles"] = dataloader.NewBatchedLoader(getFilesBatch)
loaderMap["getFile"] = dataloader.NewBatchedLoader(getFileBatch)
loaderMap["listObjects"] = dataloader.NewBatchedLoader(listObjectsBatch)
loaderMap["getDirs"] = dataloader.NewBatchedLoader(getDirsBatch)
return loaderMap
}

175
internal/graphqlTypes.go Normal file
View File

@ -0,0 +1,175 @@
package s3browser
import (
"fmt"
"path/filepath"
"strings"
"github.com/graph-gophers/dataloader"
"github.com/graphql-go/graphql"
)
var graphqlDirType *graphql.Object
var graphqlFileType *graphql.Object
// graphqlTypes create all graphql types and stores the in the global variables
func graphqlTypes() {
graphqlDirType = graphql.NewObject(graphql.ObjectConfig{
Name: "Directory",
Description: "Represents a directory",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
},
"name": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
source, ok := p.Source.(Directory)
if !ok {
return nil, fmt.Errorf("Failed to parse source for resolve")
}
return filepath.Base(source.ID), nil
},
},
},
})
graphqlFileType = graphql.NewObject(graphql.ObjectConfig{
Name: "File",
Description: "Represents a file, not a directory",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.ID),
Description: "The uniqe ID of the file. Represents the path and the s3 key.",
},
"name": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
source, ok := p.Source.(File)
if !ok {
return nil, fmt.Errorf("Failed to parse source for resolve")
}
return filepath.Base(source.ID), nil
},
},
"size": &graphql.Field{
Type: graphql.Int,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
file, err := loadFile(p)
if err != nil {
return nil, err
}
return file.Size, nil
},
},
"contentType": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
file, err := loadFile(p)
if err != nil {
return nil, err
}
return file.ContentType, nil
},
},
"etag": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
file, err := loadFile(p)
if err != nil {
return nil, err
}
return file.ETag, nil
},
},
"parent": &graphql.Field{
Type: graphqlDirType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
source, ok := p.Source.(File)
if !ok {
return nil, fmt.Errorf("Failed to parse Source for parent resolve")
}
basename := filepath.Dir(source.ID)
if basename == "." {
basename = "/"
}
return Directory{
ID: basename,
}, nil
},
},
},
})
graphqlDirType.AddFieldConfig("files", &graphql.Field{
Type: graphql.NewList(graphqlFileType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
source, ok := p.Source.(Directory)
if !ok {
return nil, fmt.Errorf("Failed to parse Source for files resolve")
}
loader := p.Context.Value("loader").(map[string]*dataloader.Loader)
thunk := loader["getFiles"].Load(p.Context, dataloader.StringKey(source.ID))
return thunk()
},
})
graphqlDirType.AddFieldConfig("directorys", &graphql.Field{
Type: graphql.NewList(graphqlDirType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
source, ok := p.Source.(Directory)
if !ok {
return nil, fmt.Errorf("Failed to parse Source for directorys resolve")
}
loader := p.Context.Value("loader").(map[string]*dataloader.Loader)
thunk := loader["getDirs"].Load(p.Context, dataloader.StringKey(source.ID))
return thunk()
},
})
graphqlDirType.AddFieldConfig("parent", &graphql.Field{
Type: graphqlDirType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
source, ok := p.Source.(Directory)
if !ok {
return nil, fmt.Errorf("Failed to parse Source for directorys resolve")
}
dirs := strings.Split(source.ID, "/")
return Directory{
ID: strings.Join(dirs[:len(dirs)-2], "/") + "/",
}, nil
},
})
}
// graphqlTypes helper func for using the dataloader to get a file
func loadFile(p graphql.ResolveParams) (*File, error) {
source, ok := p.Source.(File)
if !ok {
return nil, fmt.Errorf("Failed to parse source for resolve")
}
loader := p.Context.Value("loader").(map[string]*dataloader.Loader)
thunk := loader["getFile"].Load(p.Context, dataloader.StringKey(source.ID))
result, err := thunk()
file, ok := result.(*File)
if !ok {
return nil, fmt.Errorf("Failed to load file")
}
return file, err
}

102
internal/s3Broswer.go Normal file
View File

@ -0,0 +1,102 @@
package s3browser
import (
"context"
"log"
"net/http"
"github.com/graph-gophers/dataloader"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
// AppConfig general config
type AppConfig struct {
S3Endoint string
S3AccessKey string
S3SecretKey string
S3SSL bool
S3Buket string
}
// File represents a file with its metadata
type File struct {
ID string `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
ContentType string `json:"contentType"`
ETag string `json:"etag"`
}
// Directory represents a directory with its metadata
type Directory struct {
ID string `json:"id"`
Name string `json:"name"`
Files []File `json:"files"`
Directorys []Directory `json:"directorys"`
}
var bucketName = "dev"
// initHttp setup and start the http server. Blocking
func initHttp(schema graphql.Schema, s3Client *minio.Client, loaderMap map[string]*dataloader.Loader) {
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: false,
Playground: true,
})
resolveContext := context.WithValue(context.Background(), "s3Client", s3Client)
resolveContext = context.WithValue(resolveContext, "loader", loaderMap)
http.HandleFunc("/graphql", func(rw http.ResponseWriter, r *http.Request) {
h.ContextHandler(resolveContext, rw, r)
})
http.ListenAndServe(":8080", nil)
}
// setupS3Client connect the s3Client
func setupS3Client(config AppConfig) *minio.Client {
minioClient, err := minio.New(config.S3Endoint, &minio.Options{
Creds: credentials.NewStaticV4(config.S3AccessKey, config.S3SecretKey, ""),
Secure: config.S3SSL,
})
if err != nil {
log.Fatalln(err)
}
exists, err := minioClient.BucketExists(context.Background(), config.S3Buket)
if err != nil {
log.Fatalln(err)
}
if !exists {
log.Fatalf("Bucket '%s' does not exist", config.S3Buket)
} else {
log.Print("S3 client connected")
}
return minioClient
}
// Start starts the app
func Start(config AppConfig) {
s3Client := setupS3Client(config)
loader := createDataloader()
graphqlTypes()
schema, err := graphqlSchema()
if err != nil {
log.Panic(err)
}
initHttp(schema, s3Client, loader)
}

80
internal/schema.go Normal file
View File

@ -0,0 +1,80 @@
package s3browser
import (
"fmt"
"github.com/graph-gophers/dataloader"
"github.com/graphql-go/graphql"
)
// graphqlSchema generate the schema with its root query and mutation
func graphqlSchema() (graphql.Schema, error) {
fields := graphql.Fields{
"files": &graphql.Field{
Type: graphql.NewNonNull(graphql.NewList(graphqlFileType)),
Args: graphql.FieldConfigArgument{
"path": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
path, ok := p.Args["path"].(string)
if !ok {
return nil, nil
}
loader := p.Context.Value("loader").(map[string]*dataloader.Loader)
thunk := loader["getFiles"].Load(p.Context, dataloader.StringKey(path))
return thunk()
},
},
"directorys": &graphql.Field{
Type: graphql.NewNonNull(graphql.NewList(graphqlDirType)),
Args: graphql.FieldConfigArgument{
"path": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
path, ok := p.Args["path"].(string)
if !ok {
return nil, nil
}
loader := p.Context.Value("loader").(map[string]*dataloader.Loader)
thunk := loader["getDirs"].Load(p.Context, dataloader.StringKey(path))
return thunk()
},
},
"file": &graphql.Field{
Type: graphqlFileType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.ID),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id, ok := p.Args["id"].(string)
if !ok {
return nil, fmt.Errorf("Failed to parse args")
}
return File{
ID: id,
}, nil
},
},
}
rootQuery := graphql.ObjectConfig{
Name: "RootQuery",
Fields: fields,
}
schemaConfig := graphql.SchemaConfig{
Query: graphql.NewObject(rootQuery),
}
return graphql.NewSchema(schemaConfig)
}