Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stored procedure #3

Open
wants to merge 19 commits into
base: streamline_api_endpoint
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ docker run -v $PWD:/host --entrypoint cp cndy-store/analytics analytics /host/cn

## Latest stats

GET https://api.cndy.store/stats/latest
GET https://api.cndy.store/stats/latest?asset_code=CNDY&asset_issuer=GCJKC2MI63KSQ6MLE6GBSXPDKTDAK43WR522ZYR3F34NPM7Z5UEPIZNX

```json
{
"status": "ok",
"latest": {
"paging_token": "33825130903777281-1",
"asset_type": "credit_alphanum4",
Expand All @@ -74,12 +75,13 @@ GET https://api.cndy.store/stats/latest

## Asset stats history

GET https://api.cndy.store/stats[?from=2018-03-03T23:05:40Z&to=2018-03-03T23:05:50Z]
GET https://api.cndy.store/stats?asset_code=CNDY&asset_issuer=GCJKC2MI63KSQ6MLE6GBSXPDKTDAK43WR522ZYR3F34NPM7Z5UEPIZNX[&from=2018-03-03T23:05:40Z&to=2018-03-03T23:05:50Z]

If not set, `from` defaults to UNIX timestamp `0`, `to` to `now`.

```json
{
"status": "ok",
"stats": [
{
"paging_token": "33864305300480001-1",
Expand Down Expand Up @@ -115,18 +117,21 @@ GET https://api.cndy.store/stats/cursor

```json
{
"status": "ok",
"current_cursor": "33877250331906049-1"
}
```

## Effects

GET https://api.cndy.store/effects[?from=2018-03-03T23:05:40Z&to=2018-03-03T23:05:50Z]
GET https://api.cndy.store/effects?asset_code=CNDY&asset_issuer=GCJKC2MI63KSQ6MLE6GBSXPDKTDAK43WR522ZYR3F34NPM7Z5UEPIZNX[&from=2018-03-03T23:05:40Z&to=2018-03-03T23:05:50Z]


If not set, `from` defaults to UNIX timestamp `0`, `to` to `now`.

```json
{
"status": "ok",
"effects": [
{
"id": "0033819672000335873-0000000001",
Expand Down Expand Up @@ -174,3 +179,56 @@ If not set, `from` defaults to UNIX timestamp `0`, `to` to `now`.
}
}
```


## Assets

### Create a new asset

POST https://api.cndy.store/assets

Body

```json
{
"code": "CNDY",
"issuer": "GCJKC2MI63KSQ6MLE6GBSXPDKTDAK43WR522ZYR3F34NPM7Z5UEPIZNX"
}
```

Response

```json
{
"status": "ok",
"asset": {
"code": "CNDY",
"issuer": "GCJKC2MI63KSQ6MLE6GBSXPDKTDAK43WR522ZYR3F34NPM7Z5UEPIZNX",
"created_at": "2018-07-04T19:16:47.02965Z"
}
}
```

### Get all known assets

GET https://api.cndy.store/assets


```json
{
"status": "ok",
"assets": [
{
"type": "credit_alphanum4",
"code": "CNDY",
"issuer": "GCJKC2MI63KSQ6MLE6GBSXPDKTDAK43WR522ZYR3F34NPM7Z5UEPIZNX",
"created_at": "2018-07-04T19:16:47.02965Z"
},
{
"code": "LOCALCOIN",
"issuer": "GCJKCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"created_at": "2018-07-04T19:54:39.14328Z"
}
]
}
```
76 changes: 76 additions & 0 deletions controllers/assets/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package assets

import (
"fmt"
"github.com/cndy-store/analytics/models/asset"
"github.com/cndy-store/analytics/utils/sql"
"github.com/gin-gonic/gin"
"log"
"net/http"
)

func Init(db sql.Database, router *gin.Engine) {
router.POST("/assets", func(c *gin.Context) {
// Read JSON body and parse it into asset struct
body := asset.Asset{}
err := c.BindJSON(&body)
if err != nil {
jsonErrorMsg := fmt.Sprintf("Invalid JSON body: %s", err)
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"message": jsonErrorMsg,
})
return
}

exists, err := asset.Exists(db, body)
if err != nil {
log.Printf("[ERROR] POST /assets: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{
"status": "error",
"message": "Internal server error",
})
return
}
if exists {
c.JSON(http.StatusConflict, gin.H{
"status": "error",
"message": "Asset already exists",
})
return
}

newAsset, err := asset.New(db, body)
if err != nil {
log.Printf("[ERROR] POST /assets: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{
"status": "error",
"message": "Internal server error",
})
return
}

c.JSON(http.StatusOK, gin.H{
"status": "ok",
"asset": newAsset,
})
})

router.GET("/assets", func(c *gin.Context) {
assets, err := asset.Get(db)
if err != nil {
log.Printf("[ERROR] Couldn't get assets from database: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{
"status": "error",
"message": "Internal server error",
})
return
}

c.JSON(http.StatusOK, gin.H{
"status": "ok",
"assets": assets,
})
return
})
}
136 changes: 136 additions & 0 deletions controllers/assets/assets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package assets

import (
"bytes"
"encoding/json"
"fmt"
"github.com/cndy-store/analytics/utils/cndy"
"github.com/cndy-store/analytics/utils/sql"
"github.com/cndy-store/analytics/utils/test"
"github.com/gin-gonic/gin"
"net/http"
"net/http/httptest"
"strings"
"testing"
)

type HttpTest struct {
method string
url string
body string
statusCode int
expectedBody []string
}

func TestAssets(t *testing.T) {
db, err := sql.OpenAndMigrate("../..")
if err != nil {
t.Error(err)
}

tx, err := db.Beginx()
if err != nil {
t.Error(err)
}
defer tx.Rollback()

err = test.InsertTestData(tx)
if err != nil {
t.Error(err)
}

var tests = []HttpTest{
{
"POST",
"/assets",
`{"code": "TEST", "issuer": "GCJXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}`,
http.StatusOK,
[]string{
`"status":"ok"`,
`"code":"TEST"`,
`"issuer":"GCJXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"`,
},
},

// Check whether duplicates are prevented
{
"POST",
"/assets",
`{"code": "TEST", "issuer": "GCJXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}`,
http.StatusConflict,
[]string{
`"status":"error"`,
`"message":"Asset already exists"`,
},
},

{
"POST",
"/assets",
`{"code": "invalid`,
http.StatusBadRequest,
[]string{
`"status":"error"`,
},
},

// Check whether new asset as well as CNDY asset are present in database
{
"GET",
"/assets",
"",
http.StatusOK,
[]string{
`"status":"ok"`,
fmt.Sprintf(`"code":"%s"`, cndy.AssetCode),
fmt.Sprintf(`"issuer":"%s"`, cndy.AssetIssuer),
`"code":"TEST"`,
`"issuer":"GCJXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"`,
},
},
}

router := gin.Default()
Init(tx, router)

for _, tt := range tests {
body := bytes.NewBufferString(tt.body)
req, _ := http.NewRequest(tt.method, tt.url, body)
resp := httptest.NewRecorder()

router.ServeHTTP(resp, req)

if resp.Code != tt.statusCode {
t.Errorf("Expected code %v, got %v, for %+v", tt.statusCode, resp.Code, tt)
}

type resJson struct {
Status string
}

if tt.statusCode == http.StatusOK {
if !strings.Contains(resp.Body.String(), `"status":"ok"`) {
t.Errorf("Body did not contain ok status message: %s", resp.Body.String())
}
} else {
if !strings.Contains(resp.Body.String(), `"status":"error"`) {
t.Errorf("Body did not contain error status message: %s", resp.Body.String())
}

// Skip to next test
continue
}

res := resJson{}
err := json.Unmarshal([]byte(resp.Body.String()), &res)
if err != nil {
t.Error(err)
}

for _, contains := range tt.expectedBody {
if !strings.Contains(resp.Body.String(), contains) {
t.Errorf("Body did not contain '%s' in '%s'", contains, resp.Body.String())
}
}
}
}
Loading