~evanj/cms

93b7c375d5c5abf4c5790c125d608e50154d3b86 — Evan M Jones 8 months ago 934d783
Feat(e3): Making E3 integration better. Adding tests. Potentially use as
future ref to pointer users toward to help them implement E3.
4 files changed, 117 insertions(+), 21 deletions(-)

M internal/c/content/content.go
M pkg/e3/e3.go
M pkg/e3/e3_test.go
A pkg/e3/testfiles/zoidberg.png
M internal/c/content/content.go => internal/c/content/content.go +5 -3
@@ 1,9 1,10 @@
package content

import (
	"context"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"strings"



@@ 37,7 38,7 @@ type dber interface {
}

type e3er interface {
	Upload(file multipart.File, hdr *multipart.FileHeader) (url string, err error)
	Upload(ctx context.Context, public bool, filename string, file io.Reader) (string, error)
}

func New(log *log.Logger, db dber, e3 e3er) *Content {


@@ 91,7 92,8 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {
			return
		}

		url, err := c.e3.Upload(file, header)
		// TODO: Change public to false
		url, err := c.e3.Upload(r.Context(), true, header.Filename, file)
		if err != nil {
			c.log.Println("failed to upload file", err)
			c.Error(w, r, http.StatusInternalServerError, "failed to upload file")

M pkg/e3/e3.go => pkg/e3/e3.go +52 -18
@@ 2,6 2,7 @@ package e3

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"io/ioutil"


@@ 12,24 13,34 @@ import (

type E3 struct {
	user, pass, url string
	client          *http.Client
}

const DefaultURL = "https://e3.evanjon.es/api"

func New(user, pass, url string) *E3 {
	return &E3{
		user,
		pass,
		url,
		http.DefaultClient,
	}
}

func WithClient(user, pass, url string, client *http.Client) *E3 {
	return &E3{
		user,
		pass,
		url,
		client,
	}
}

// TODO: Everything is public by default at the moment. FIX THIS. Private by
// default and proxy requests on behalf of users.
func (e3 *E3) Upload(file multipart.File, hdr *multipart.FileHeader) (string, error) {
func (e3 *E3) Upload(ctx context.Context, public bool, filename string, file io.Reader) (string, error) {
	var requestBody bytes.Buffer
	mpwriter := multipart.NewWriter(&requestBody)

	// File field.
	filewriter, err := mpwriter.CreateFormFile("data", hdr.Filename)
	filewriter, err := mpwriter.CreateFormFile("data", filename)
	if err != nil {
		return "", err
	}


@@ 37,22 48,23 @@ func (e3 *E3) Upload(file multipart.File, hdr *multipart.FileHeader) (string, er
		return "", err
	}

	// Acces field (TODO: Remove).
	fieldwriter, err := mpwriter.CreateFormField("access")
	if err != nil {
		return "", err
	}
	_, err = fieldwriter.Write([]byte("public"))
	if err != nil {
		return "", err
	if public {
		fieldwriter, err := mpwriter.CreateFormField("access")
		if err != nil {
			return "", err
		}
		_, err = fieldwriter.Write([]byte("public"))
		if err != nil {
			return "", err
		}
	}

	if err := mpwriter.Close(); err != nil {
		return "", err
	}

	// Request as usual.
	// TODO: Add timeout.
	req, err := http.NewRequest("POST", e3.url, &requestBody)
	req, err := http.NewRequestWithContext(ctx, "POST", e3.url, &requestBody)
	if err != nil {
		return "", err
	}


@@ 60,7 72,7 @@ func (e3 *E3) Upload(file multipart.File, hdr *multipart.FileHeader) (string, er
	req.SetBasicAuth(e3.user, e3.pass)
	req.Header.Set("Content-Type", mpwriter.FormDataContentType())

	resp, err := http.DefaultClient.Do(req)
	resp, err := e3.client.Do(req)
	if err != nil {
		return "", err
	}


@@ 71,9 83,31 @@ func (e3 *E3) Upload(file multipart.File, hdr *multipart.FileHeader) (string, er
		return "", err
	}

	res := strings.TrimSpace(string(bytes))
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("%s", strings.TrimSpace(string(bytes)))
		return "", fmt.Errorf("%s", res)
	}
	return res, nil
}

func (e3 *E3) Proxy(ctx context.Context, objectURL string) ([]byte, error) {
	req, err := http.NewRequestWithContext(ctx, "GET", objectURL, nil)
	if err != nil {
		return []byte{}, err
	}

	req.SetBasicAuth(e3.user, e3.pass)

	resp, err := e3.client.Do(req)
	if err != nil {
		return []byte{}, err
	}
	defer resp.Body.Close()

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return []byte{}, err
	}

	return strings.TrimSpace(string(bytes)), nil
	return bytes, nil
}

M pkg/e3/e3_test.go => pkg/e3/e3_test.go +60 -0
@@ 1,1 1,61 @@
package e3_test

import (
	"context"
	"io/ioutil"
	"net/http"
	"os"
	"testing"

	"git.sr.ht/~evanj/cms/pkg/e3"
	"github.com/go-playground/assert/v2"
)

var objectstorage = e3.New(
	os.Getenv("TEST_E3_USER"),
	os.Getenv("TEST_E3_PASS"),
	e3.DefaultURL,
)

func TestPublic(t *testing.T) {
	t.Parallel()

	// Upload public file.
	file, err := os.Open("testfiles/zoidberg.png")
	assert.Equal(t, nil, err)
	url, err := objectstorage.Upload(context.Background(), true, file.Name(), file)
	assert.Equal(t, nil, err)
	// Retrieve the same file.
	req, err := http.NewRequest("GET", url, nil)
	assert.Equal(t, nil, err)
	resp, err := http.DefaultClient.Do(req)
	assert.Equal(t, nil, err)
	defer resp.Body.Close()
	_, err = ioutil.ReadAll(resp.Body)
	assert.Equal(t, nil, err)
	assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestPrivate(t *testing.T) {
	t.Parallel()

	// Upload public file.
	file, err := os.Open("testfiles/zoidberg.png")
	assert.Equal(t, nil, err)
	url, err := objectstorage.Upload(context.Background(), false, file.Name(), file)
	assert.Equal(t, nil, err)

	// Retrieve the same file (without creds).
	req, err := http.NewRequest("GET", url, nil)
	assert.Equal(t, nil, err)
	resp, err := http.DefaultClient.Do(req)
	assert.Equal(t, nil, err)
	defer resp.Body.Close()
	_, err = ioutil.ReadAll(resp.Body)
	assert.Equal(t, nil, err)
	assert.NotEqual(t, http.StatusOK, resp.StatusCode)

	// Retrieve the same file (with creds).
	_, err = objectstorage.Proxy(context.Background(), url)
	assert.Equal(t, nil, err)
}

A pkg/e3/testfiles/zoidberg.png => pkg/e3/testfiles/zoidberg.png +0 -0