Skip to content
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