Compare commits

...

7 Commits

Author SHA1 Message Date
125ce9c955 fix get value db 2022-02-07 15:59:52 +01:00
131f19deed db interface 2022-02-07 15:59:43 +01:00
2ac552e840 added gql tests 2022-02-07 15:56:09 +01:00
48c50a5b7e typesInit flag 2022-02-07 15:53:14 +01:00
48f770f703 added loader tests 2021-11-27 04:07:51 +01:00
2ae14cdfd4 fixed mock s3 list dirs 2021-11-27 04:07:41 +01:00
a10593a318 loader cache config 2021-11-27 04:07:27 +01:00
10 changed files with 323 additions and 97 deletions

View File

@@ -1,94 +1,9 @@
package db
import (
"context"
"database/sql"
_ "embed"
"time"
import "context"
_ "github.com/go-sql-driver/mysql"
"golang.org/x/crypto/bcrypt"
)
//go:embed setup.sql
var setupSql string
const DB_NAME = "s3Browser"
type DB struct {
dbConn *sql.DB
}
func NewDB(dataSourceName string) (*DB, error) {
db, err := sql.Open("mysql", dataSourceName)
if err != nil {
return nil, err
}
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
return &DB{
dbConn: db,
}, nil
}
func (d *DB) Setup() error {
tx, err := d.dbConn.Begin()
if err != nil {
return err
}
_, err = tx.Exec(setupSql)
if err != nil {
tx.Rollback()
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (d *DB) CheckLogin(ctx context.Context, username, password string) (bool, error) {
rows, err := d.dbConn.QueryContext(ctx, "SELECT password FROM user WHERE username = ?", username)
if err != nil {
return false, err
}
if !rows.Next() {
return false, nil
}
var passwordHash []byte
err = rows.Scan(&passwordHash)
if err != nil {
return false, err
}
if bcrypt.CompareHashAndPassword(passwordHash, []byte(password)) != nil {
return false, nil
}
return true, nil
}
func (d *DB) AddUser(ctx context.Context, username, password string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
_, err = d.dbConn.ExecContext(ctx, "INSERT INTO user (username,password) VALUES (?,?)", username, hash)
if err != nil {
return err
}
return nil
type DB interface {
Setup() error
CheckLogin(ctx context.Context, username, password string) (bool, error)
AddUser(ctx context.Context, username, password string) error
}

94
internal/db/mysql.go Normal file
View File

@@ -0,0 +1,94 @@
package db
import (
"context"
"database/sql"
_ "embed"
"time"
_ "github.com/go-sql-driver/mysql"
"golang.org/x/crypto/bcrypt"
)
//go:embed setup.sql
var setupSql string
const DB_NAME = "s3Browser"
type mysqlDB struct {
dbConn *sql.DB
}
func NewDB(dataSourceName string) (DB, error) {
db, err := sql.Open("mysql", dataSourceName)
if err != nil {
return nil, err
}
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
return &mysqlDB{
dbConn: db,
}, nil
}
func (d *mysqlDB) Setup() error {
tx, err := d.dbConn.Begin()
if err != nil {
return err
}
_, err = tx.Exec(setupSql)
if err != nil {
tx.Rollback()
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (d *mysqlDB) CheckLogin(ctx context.Context, username, password string) (bool, error) {
rows, err := d.dbConn.QueryContext(ctx, "SELECT password FROM user WHERE username = ?", username)
if err != nil {
return false, err
}
if !rows.Next() {
return false, nil
}
var passwordHash []byte
err = rows.Scan(&passwordHash)
if err != nil {
return false, err
}
if bcrypt.CompareHashAndPassword(passwordHash, []byte(password)) != nil {
return false, nil
}
return true, nil
}
func (d *mysqlDB) AddUser(ctx context.Context, username, password string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
_, err = d.dbConn.ExecContext(ctx, "INSERT INTO user (username,password) VALUES (?,?)", username, hash)
if err != nil {
return err
}
return nil
}

75
internal/gql/gql_test.go Normal file
View File

@@ -0,0 +1,75 @@
package gql_test
import (
"context"
"testing"
"git.kapelle.org/niklas/s3browser/internal/gql"
"git.kapelle.org/niklas/s3browser/internal/loader"
"git.kapelle.org/niklas/s3browser/internal/s3"
"github.com/graph-gophers/dataloader"
"github.com/graphql-go/graphql"
"github.com/stretchr/testify/assert"
)
func setup(t *testing.T) (*assert.Assertions, context.Context, graphql.Schema) {
assert := assert.New(t)
ctx := context.Background()
schema, _ := gql.GraphqlSchema()
s3, err := s3.NewMockS3([]string{"bucket1"})
assert.NoError(err)
ctx = context.WithValue(ctx, "s3Client", s3)
loader := loader.NewLoader(loader.CacheConfig{
ListObjectsLoaderCache: &dataloader.NoCache{},
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
StatObjectLoaderCache: &dataloader.NoCache{},
ListBucketsLoaderCache: &dataloader.NoCache{},
})
assert.NotNil(loader)
ctx = context.WithValue(ctx, "loader", loader)
return assert, ctx, schema
}
func do(ctx context.Context, schema graphql.Schema, query string) *graphql.Result {
params := graphql.Params{
Schema: schema,
RequestString: query,
Context: ctx,
}
r := graphql.Do(params)
return r
}
func TestCreateSchema(t *testing.T) {
assert := assert.New(t)
assert.NotPanics(func() {
gql.GraphqlTypes()
})
var schema graphql.Schema
var err error
assert.NotPanics(func() {
schema, err = gql.GraphqlSchema()
})
assert.NoError(err)
assert.NotNil(schema)
}
func TestAuth(t *testing.T) {
assert, ctx, schema := setup(t)
r := do(ctx, schema, `
{
authorized
}
`)
t.Logf("Data: %v", r.Data)
assert.Len(r.Errors, 0)
}

View File

@@ -13,6 +13,7 @@ import (
types "git.kapelle.org/niklas/s3browser/internal/types"
)
var typesInit bool = false
var graphqlDirType *graphql.Object
var graphqlFileType *graphql.Object
var graphqlLoginResultType *graphql.Object
@@ -246,6 +247,8 @@ func GraphqlTypes() {
},
})
typesInit = true
}
//loadFile helper func for using the dataloader to get a file

View File

@@ -222,7 +222,7 @@ func deleteDirectory(ctx context.Context, id types.ID) error {
//login Checks for valid username password combination. Returns singed jwt string
func login(ctx context.Context, username, password string) (types.LoginResult, error) {
dbStore := ctx.Value("dbStore").(*db.DB)
dbStore := ctx.Value("dbStore").(db.DB)
succes, err := dbStore.CheckLogin(ctx, username, password)

View File

@@ -15,6 +15,10 @@ import (
//GraphqlSchema generate the schema with its root query and mutation
func GraphqlSchema() (graphql.Schema, error) {
if !typesInit {
GraphqlTypes()
}
queryFields := graphql.Fields{
"files": &graphql.Field{
Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(graphqlFileType))),

View File

@@ -23,11 +23,18 @@ type Loader struct {
listBucketsLoaderCache cache.S3Cache
}
func NewLoader(config types.AppConfig) *Loader {
listObjectsLoaderCache := &dataloader.NoCache{}
listObjectsRecursiveLoaderCache := &dataloader.NoCache{}
statObjectLoaderCache := cache.NewTTLCache(config.CacheTTL, config.CacheCleanup)
listBucketsLoaderCache := cache.NewTTLCache(config.CacheTTL, config.CacheCleanup)
type CacheConfig struct {
ListObjectsLoaderCache cache.S3Cache
ListObjectsRecursiveLoaderCache cache.S3Cache
StatObjectLoaderCache cache.S3Cache
ListBucketsLoaderCache cache.S3Cache
}
func NewLoader(cacheConfig CacheConfig) *Loader {
listObjectsLoaderCache := cacheConfig.ListObjectsLoaderCache
listObjectsRecursiveLoaderCache := cacheConfig.ListObjectsRecursiveLoaderCache
statObjectLoaderCache := cacheConfig.StatObjectLoaderCache
listBucketsLoaderCache := cacheConfig.ListBucketsLoaderCache
return &Loader{
listObjectsLoader: dataloader.NewBatchedLoader(

View File

@@ -0,0 +1,105 @@
package loader_test
import (
"context"
"strings"
"testing"
"git.kapelle.org/niklas/s3browser/internal/loader"
"git.kapelle.org/niklas/s3browser/internal/s3"
"git.kapelle.org/niklas/s3browser/internal/types"
"github.com/graph-gophers/dataloader"
"github.com/stretchr/testify/assert"
)
func setup(t *testing.T) (context.Context, *loader.Loader, *assert.Assertions) {
assert := assert.New(t)
s3, _ := s3.NewMockS3([]string{"bucket1", "bucket2"})
loader := loader.NewLoader(loader.CacheConfig{
ListObjectsLoaderCache: &dataloader.NoCache{},
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
StatObjectLoaderCache: &dataloader.NoCache{},
ListBucketsLoaderCache: &dataloader.NoCache{},
})
fillS3(s3)
ctx := context.WithValue(context.Background(), "s3Client", s3)
return ctx, loader, assert
}
func fillS3(s3 s3.S3Service) {
ctx := context.Background()
length := int64(len("content"))
for _, v := range []string{
"bucket1:/file1", "bucket1:/file2", "bucket1:/dir1/file1",
"bucket1:/dir1/file2", "bucket1:/dir2/file1", "bucket1:/dir1/sub1/file1",
"bucket1:/dir1/sub1/file2",
} {
s3.PutObject(ctx, *types.ParseID(v), strings.NewReader("content"), length)
}
}
func TestCreateLoader(t *testing.T) {
assert := assert.New(t)
loader := loader.NewLoader(loader.CacheConfig{
ListObjectsLoaderCache: &dataloader.NoCache{},
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
StatObjectLoaderCache: &dataloader.NoCache{},
ListBucketsLoaderCache: &dataloader.NoCache{},
})
assert.NotNil(loader)
}
func TestGetBuckets(t *testing.T) {
ctx, loader, assert := setup(t)
buckets, err := loader.GetBuckets(ctx)
assert.NoError(err)
assert.Len(buckets, 2)
assert.Contains(buckets, "bucket1")
assert.Contains(buckets, "bucket2")
}
func TestGetFile(t *testing.T) {
ctx, loader, assert := setup(t)
file, err := loader.GetFile(ctx, *types.ParseID("bucket1:/dir1/file1"))
assert.NoError(err)
assert.Equal("bucket1:/dir1/file1", file.ID.String())
assert.Equal("file1", file.Name)
assert.Equal(int64(len("content")), file.Size)
}
func TestGetFiles(t *testing.T) {
ctx, loader, assert := setup(t)
id := types.ParseID("bucket1:/")
files, err := loader.GetFiles(ctx, *id)
assert.NoError(err)
assert.Len(files, 2)
}
func TestGetDir(t *testing.T) {
ctx, loader, assert := setup(t)
id := types.ParseID("bucket1:/")
dirs, err := loader.GetDirs(ctx, *id)
assert.NoError(err)
assert.Len(dirs, 2)
}
func Test(t *testing.T) {
ctx, loader, assert := setup(t)
id := types.ParseID("bucket1:/dir1/")
files, err := loader.GetFilesRecursive(ctx, *id)
assert.NoError(err)
assert.Len(files, 4)
}

View File

@@ -47,14 +47,30 @@ func (m *mockS3) ListBuckets(ctx context.Context) ([]string, error) {
func (m *mockS3) ListObjects(ctx context.Context, id types.ID) ([]Object, error) {
var results []Object
dirs := make(map[string]bool)
depth := len(strings.Split(id.Key, "/"))
for k, v := range m.objects {
if k.Bucket == id.Bucket {
if k.Parent().Key == id.Key {
results = append(results, *mockObjToObject(v, k))
} else if strings.HasPrefix(k.Key, id.Key) {
s := strings.Join(strings.Split(k.Key, "/")[:depth], "/") + "/"
dirs[s] = true
}
}
}
for k := range dirs {
results = append(results, Object{
ID: types.ID{
Bucket: id.Bucket,
Key: k,
},
})
}
return results, nil
}

View File

@@ -3,8 +3,10 @@ package s3browser
import (
"context"
"github.com/graph-gophers/dataloader"
log "github.com/sirupsen/logrus"
"git.kapelle.org/niklas/s3browser/internal/cache"
"git.kapelle.org/niklas/s3browser/internal/db"
gql "git.kapelle.org/niklas/s3browser/internal/gql"
httpserver "git.kapelle.org/niklas/s3browser/internal/httpserver"
@@ -33,7 +35,12 @@ func Start(config types.AppConfig) {
log.Error("Failed to connect DB: ", err.Error())
}
loader := loader.NewLoader(config)
loader := loader.NewLoader(loader.CacheConfig{
ListObjectsLoaderCache: &dataloader.NoCache{},
ListObjectsRecursiveLoaderCache: &dataloader.NoCache{},
StatObjectLoaderCache: cache.NewTTLCache(config.CacheTTL, config.CacheCleanup),
ListBucketsLoaderCache: cache.NewTTLCache(config.CacheTTL, config.CacheCleanup),
})
gql.GraphqlTypes()
schema, err := gql.GraphqlSchema()