Skip to content

Commit

Permalink
Merge pull request #1 from gitcoinco/init
Browse files Browse the repository at this point in the history
initial implementation
  • Loading branch information
gravityblast authored Oct 24, 2023
2 parents e16f8f4 + bc6ffa7 commit 5797e8a
Show file tree
Hide file tree
Showing 9 changed files with 516 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/public
/bin
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/pina
/public
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM golang:1.21.3-alpine3.18

COPY . /app
WORKDIR /app

RUN mkdir /app/bin
RUN mkdir /app/public

RUN CGO_ENABLED=0 GOOS=linux go build -o /app/bin/🍍

EXPOSE 8000

CMD /app/bin/🍍 -port 8000 -public /app/public
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: docker-build docker-run docker-kill docker-stop docker-logs docker-deploy-contracts docker-all

IMAGE_NAME=pina
CONTAINER_NAME=pina

docker-all: docker-kill docker-build docker-run

docker-build:
docker build . -t $(IMAGE_NAME) --no-cache --progress=plain

docker-run:
docker run --name $(CONTAINER_NAME) --rm -d -p 127.0.0.1:8000:8000/tcp $(IMAGE_NAME)

docker-kill:
-docker kill $(CONTAINER_NAME)

docker-stop:
docker stop $(CONTAINER_NAME)

docker-logs:
docker logs -f $(CONTAINER_NAME)

32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Piña 🍍

Piña is a clone of the [Pinata](https://www.pinata.cloud/) API,
**exclusively designed for local development environments** to streamline your development process and minimize
the need for direct usage of Pinata's services during development.

## API

```
get /ipfs/{CID}
post /pinning/pinJSONToIPFS
post /pinning/pinFileToIPFS
```

### Test and Build

```
git clone [email protected]:gitcoinco/pina.git
go test
go build
```

### Run

`go build && ./pina -port 8000 -public ./public`

### Run in docker

```
make docker-build
make docker-run
```
25 changes: 25 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module github.com/gitcoinco/pina

go 1.19

require (
github.com/gravityblast/miniassert v0.0.0-20140522125902-bee63581261a
github.com/julienschmidt/httprouter v1.3.0
)

require (
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.4 // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multibase v0.0.3 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.0.15 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect
github.com/rs/cors v1.10.1 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
)
41 changes: 41 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
github.com/gravityblast/miniassert v0.0.0-20140522125902-bee63581261a h1:3xcty4gUzOHD4pqUZQDbS/Xb34susIRg9nmjKR3XM94=
github.com/gravityblast/miniassert v0.0.0-20140522125902-bee63581261a/go.mod h1:tVdF3zmaPraSn1jEeaVFIw4GYDX6fezNB3P9Gk4UpPA=
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4=
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM=
github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg=
github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
158 changes: 158 additions & 0 deletions handlers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/textproto"
"os"
"path/filepath"
"strings"
"testing"

assert "github.com/gravityblast/miniassert"
"github.com/julienschmidt/httprouter"
)

func newTestRouter(t *testing.T) (*httprouter.Router, string) {
publicPath := t.TempDir()
router, err := newRouter(publicPath)
if err != nil {
t.Error(err)
t.FailNow()
}

return router, filepath.Join(publicPath, "ipfs")
}

func TestIndexHandler(t *testing.T) {
router, _ := newTestRouter(t)

w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/", nil)
router.ServeHTTP(w, req)

resp := w.Result()
body, _ := io.ReadAll(resp.Body)

assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
assert.Equal(t, "Hello World", string(body))
}

func TestStaticFiles(t *testing.T) {
router, ipfsPath := newTestRouter(t)
filePath := filepath.Join(ipfsPath, "test.txt")
err := os.WriteFile(filePath, []byte("Static file example"), 0755)
if err != nil {
t.Fatal(err)
}

w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/ipfs/test.txt", nil)
router.ServeHTTP(w, req)

resp := w.Result()
body, _ := io.ReadAll(resp.Body)

assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))
assert.Equal(t, "Static file example", string(body))
}

func TestPinJSONHandler(t *testing.T) {
router, ipfsPath := newTestRouter(t)

w := httptest.NewRecorder()
reqBody := bytes.NewBufferString(`{"pinataContent": {"foo": {"bar": "baz"}}}`)
req := httptest.NewRequest(http.MethodPost, "/pinning/pinJSONToIPFS", reqBody)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", AUTH_TOKEN))
router.ServeHTTP(w, req)

resp := w.Result()

assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))

var responseBody PinJSONResponseBody
err := json.NewDecoder(resp.Body).Decode(&responseBody)
if err != nil {
log.Fatal(err)
}

cid := "bafkreihktyturq4bzrikdjylvvjbrgh5rfigzlydmjoyri3ip6fjbcqddu"
assert.Equal(t, cid, responseBody.IpfsHash)
assert.Equal(t, 10, responseBody.PinSize)
assert.NotEqual(t, "", responseBody.Timestamp)

fileName := filepath.Join(ipfsPath, cid)
f, err := os.Open(fileName)
if err != nil {
log.Fatal(err)
}

content, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}

assert.Equal(t, `{"foo":{"bar":"baz"}}`, strings.TrimSpace(string(content)))
}

func TestPinFileHandler(t *testing.T) {
router, _ := newTestRouter(t)

body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
// writer.WriteField("file", "Uploaded content")

// Create the file part with an invalid file extension
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "file", "uploaded.txt"))
h.Set("Content-Type", "text/plain")
part, _ := writer.CreatePart(h)
part.Write([]byte("Uploaded content"))
writer.Close()
// Prepare and send the HTTP request

w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/pinning/pinFileToIPFS", body)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", AUTH_TOKEN))
req.Header.Add("Content-Type", writer.FormDataContentType())
router.ServeHTTP(w, req)

resp := w.Result()

assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, "text/plain; charset=utf-8", resp.Header.Get("Content-Type"))

var responseBody PinJSONResponseBody
err := json.NewDecoder(resp.Body).Decode(&responseBody)
if err != nil {
log.Fatal(err)
}

cid := "bafkreifqrpvngn6k2q6qahrm2oawbtrsoucxi7xtxehydodbzgnky6eiem"
assert.Equal(t, cid, responseBody.IpfsHash)
assert.Equal(t, 10, responseBody.PinSize)
assert.NotEqual(t, "", responseBody.Timestamp)

fileName := filepath.Join(ipfsPath, cid)
f, err := os.Open(fileName)
if err != nil {
log.Fatal(err)
}

content, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}

assert.Equal(t, "Uploaded content", strings.TrimSpace(string(content)))
}
Loading

0 comments on commit 5797e8a

Please sign in to comment.