inital commit

This commit is contained in:
2022-05-09 14:52:18 +02:00
commit d88552c57f
12 changed files with 461 additions and 0 deletions

81
internal/client/client.go Normal file
View File

@@ -0,0 +1,81 @@
package client
import (
"context"
"errors"
"math/rand"
"time"
"git.kapelle.org/niklas/s3share/internal/db"
"git.kapelle.org/niklas/s3share/internal/s3"
"git.kapelle.org/niklas/s3share/internal/types"
)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
type Client struct {
db db.DB
s3 s3.S3
}
func NewClient(db db.DB, s3 s3.S3) *Client {
rand.Seed(time.Now().UnixNano())
return &Client{
db: db,
s3: s3,
}
}
func createRandomString() string {
s := make([]rune, 6)
for i := range s {
s[i] = letters[rand.Intn(len(letters))]
}
return string(s)
}
func (c *Client) CreateValidSlug(ctx context.Context) (string, error) {
for i := 0; i < 10; i++ {
slug := createRandomString()
res, err := c.db.GetShare(ctx, slug)
if err != nil {
return "", err
}
if res == nil {
return slug, nil
}
}
return "", errors.New("could not create valid slug after 10 tries")
}
func (c *Client) GetShare(ctx context.Context, slug string) (*types.Share, error) {
return c.db.GetShare(ctx, slug)
}
func (c *Client) CreateShare(ctx context.Context, key string) (*types.Share, error) {
slug, err := c.CreateValidSlug(ctx)
if err != nil {
return nil, err
}
share := &types.Share{
Slug: slug,
Key: key,
}
// TODO: check if key exists
err = c.db.CreateShare(ctx, share)
if err != nil {
return nil, err
}
return share, nil
}
func (c *Client) GetObjectFromShare(ctx context.Context, share *types.Share) (s3.ObjectReader, error) {
return c.s3.GetObject(ctx, share.Key)
}

14
internal/db/db.go Normal file
View File

@@ -0,0 +1,14 @@
package db
import (
"context"
"git.kapelle.org/niklas/s3share/internal/types"
)
type DB interface {
GetShare(ctx context.Context, slug string) (*types.Share, error)
CreateShare(ctx context.Context, share *types.Share) error
DeleteShare(ctx context.Context, slug string) error
Close() error
}

83
internal/db/sqlite.go Normal file
View File

@@ -0,0 +1,83 @@
package db
import (
"context"
"database/sql"
"os"
_ "embed"
_ "github.com/mattn/go-sqlite3"
"github.com/sirupsen/logrus"
"git.kapelle.org/niklas/s3share/internal/types"
)
//go:embed sqlite.sql
var setupSql string
type sqlLiteDB struct {
db *sql.DB
}
func NewSqlLiteDB(path string) (DB, error) {
os.Remove(path)
logrus.Info("Opening database")
db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
logrus.Info("Initializing database")
_, err = db.Exec(setupSql)
if err != nil {
return nil, err
}
return &sqlLiteDB{
db: db,
}, nil
}
func (db *sqlLiteDB) GetShare(ctx context.Context, slug string) (*types.Share, error) {
res, err := db.db.QueryContext(ctx, "SELECT slug, objKey FROM shares WHERE slug = ?", slug)
if err != nil {
return nil, err
}
if !res.Next() {
return nil, nil
}
var share types.Share
err = res.Scan(&share.Slug, &share.Key)
if err != nil {
return nil, err
}
return &share, nil
}
func (db *sqlLiteDB) CreateShare(ctx context.Context, share *types.Share) error {
_, err := db.db.ExecContext(ctx, "INSERT INTO shares (slug, objKey) VALUES (?, ?)", share.Slug, share.Key)
if err != nil {
return err
}
return nil
}
func (db *sqlLiteDB) DeleteShare(ctx context.Context, slug string) error {
_, err := db.db.ExecContext(ctx, "DELETE FROM shares WHERE slug = ?", slug)
if err != nil {
return err
}
return nil
}
func (db *sqlLiteDB) Close() error {
return db.db.Close()
}

5
internal/db/sqlite.sql Normal file
View File

@@ -0,0 +1,5 @@
CREATE TABLE `shares` (
slug VARCHAR(6) NOT NULL PRIMARY KEY,
objKey TEXT NOT NULL
);

41
internal/s3/minio.go Normal file
View File

@@ -0,0 +1,41 @@
package s3
import (
"context"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/sirupsen/logrus"
)
type minioClient struct {
client *minio.Client
bucket string
}
func NewMinio(endpoint, bucket, accessKey, secretAccessKey string, ssl bool) (S3, error) {
logrus.Info("Creating minio client")
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKey, secretAccessKey, ""),
Secure: ssl,
})
if err != nil {
return nil, err
}
return &minioClient{
client: client,
bucket: bucket,
}, nil
}
func (m *minioClient) GetObject(ctx context.Context, key string) (ObjectReader, error) {
object, err := m.client.GetObject(ctx, m.bucket, key, minio.GetObjectOptions{})
if err != nil {
return nil, err
}
return object, nil
}

17
internal/s3/s3.go Normal file
View File

@@ -0,0 +1,17 @@
package s3
import (
"context"
"io"
)
type ObjectReader interface {
io.Reader
io.Seeker
io.ReaderAt
io.Closer
}
type S3 interface {
GetObject(ctx context.Context, key string) (ObjectReader, error)
}

34
internal/s3Share.go Normal file
View File

@@ -0,0 +1,34 @@
package s3share
import (
"git.kapelle.org/niklas/s3share/internal/client"
"git.kapelle.org/niklas/s3share/internal/db"
"git.kapelle.org/niklas/s3share/internal/s3"
"git.kapelle.org/niklas/s3share/internal/web"
)
func Start() {
db, err := db.NewSqlLiteDB("foo.db")
if err != nil {
panic(err)
}
s3Client, err := s3.NewMinio("localhost:9000", "testo", "testo", "hunter22", false)
if err != nil {
panic(err)
}
client := client.NewClient(db, s3Client)
// share, err := client.CreateShare(context.Background(), "/go.mod")
// if err != nil {
// panic(err)
// }
// logrus.Info(share.Slug)
err = web.StartWebserver("localhost:8080", *client)
if err != nil {
panic(err)
}
}

6
internal/types/share.go Normal file
View File

@@ -0,0 +1,6 @@
package types
type Share struct {
Slug string `json:"slug"`
Key string `json:"key"`
}

85
internal/web/web.go Normal file
View File

@@ -0,0 +1,85 @@
package web
import (
"encoding/json"
"io"
"net/http"
"git.kapelle.org/niklas/s3share/internal/client"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type createShare struct {
Key string `json:"key"`
}
func StartWebserver(addr string, client client.Client) error {
r := mux.NewRouter()
r.HandleFunc("/{slug:[a-zA-Z0-9]{6}}", func(w http.ResponseWriter, r *http.Request) {
})
r.HandleFunc("/s/{slug:[a-zA-Z0-9]{6}}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
share, err := client.GetShare(r.Context(), vars["slug"])
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if share == nil {
http.NotFound(w, r)
return
}
obj, err := client.GetObjectFromShare(r.Context(), share)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = io.Copy(w, obj)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
})
r.HandleFunc("/api/share", func(w http.ResponseWriter, r *http.Request) {
}).Methods("GET")
r.HandleFunc("/api/share", func(w http.ResponseWriter, r *http.Request) {
// TODO: check auth
var shareParams createShare
err := json.NewDecoder(r.Body).Decode(&shareParams)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
share, err := client.CreateShare(r.Context(), shareParams.Key)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(share)
}).Methods("POST")
r.HandleFunc("/api/share", func(w http.ResponseWriter, r *http.Request) {
}).Methods("DELETE")
logrus.Info("Starting webserver")
return http.ListenAndServe(addr, r)
}