blob: 1a39a2e90c2fe686dc56fe1612edc58717bb8ce8 [file] [log] [blame]
// Stores and retrieves jsfiddles in Google Storage.
package store
import (
"context"
"crypto/sha256"
"fmt"
"io"
"strings"
"cloud.google.com/go/storage"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/skerr"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
)
const (
JSFIDDLE_STORAGE_BUCKET = "skia-jsfiddle"
)
// Store is used to read and write user code and media to and from Google
// Storage.
type Store interface {
// PutCode writes the code to Google Storage.
// Returns the fiddleHash.
PutCode(code, fiddleType string) (string, error)
// GetCode reads the code from Google Storage.
// Returns the code.
GetCode(hash, fiddleType string) (string, error)
}
// store implements Store.
type store struct {
bucket *storage.BucketHandle
}
// New creates a new store.
//
// local - True if running locally.
func New(ctx context.Context, local bool) (*store, error) {
ts, err := google.DefaultTokenSource(ctx, auth.ScopeReadWrite)
if err != nil {
return nil, skerr.Wrapf(err, "problem setting up client OAuth")
}
client := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(client))
if err != nil {
return nil, skerr.Wrapf(err, "problem creating storage client")
}
return &store{
bucket: storageClient.Bucket(JSFIDDLE_STORAGE_BUCKET),
}, nil
}
// PutCode implements Store.
func (s *store) PutCode(code, fiddleType string) (string, error) {
hash := computeHash(code)
path := strings.Join([]string{fiddleType, hash, "draw.js"}, "/")
w := s.bucket.Object(path).NewWriter(context.Background())
w.ObjectAttrs.ContentEncoding = "text/plain"
if n, err := w.Write([]byte(code)); err != nil {
_ = w.Close()
return "", skerr.Wrapf(err, "There was a problem storing the code. Uploaded %d bytes", n)
}
if err := w.Close(); err != nil {
return "", skerr.Wrap(err)
}
return hash, nil
}
// GetCode implements Store.
func (s *store) GetCode(hash, fiddleType string) (string, error) {
path := strings.Join([]string{fiddleType, hash, "draw.js"}, "/")
o := s.bucket.Object(path)
r, err := o.NewReader(context.Background())
if err != nil {
return "", skerr.Wrapf(err, "failed to open source file for %s", hash)
}
b, err := io.ReadAll(r)
if err != nil {
return "", skerr.Wrapf(err, "failed to read source file for %s", hash)
}
return string(b), nil
}
func computeHash(code string) string {
sum := sha256.Sum256([]byte(code))
return fmt.Sprintf("%x", sum)
}
// Confirm the *store implements Store.
var _ Store = (*store)(nil)