Skip to content

Commit e78d8b1

Browse files
authored
Merge pull request #28 from kintone/cursor_api
Cursor api
2 parents 803b30c + 8574164 commit e78d8b1

File tree

5 files changed

+265
-19
lines changed

5 files changed

+265
-19
lines changed

app.go

Lines changed: 143 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
)
2525

2626
const (
27-
NAME = "kintone-go-SDK"
27+
NAME = "go-kintone"
2828
VERSION = "0.3.0"
2929
DEFAULT_TIMEOUT = time.Second * 600 // Default value for App.Timeout
3030
)
@@ -124,7 +124,7 @@ type App struct {
124124
basicAuth bool // true to use Basic Authentication.
125125
basicAuthUser string // User name for Basic Authentication.
126126
basicAuthPassword string // Password for Basic Authentication.
127-
userAgentHeader string // User-agent request header string
127+
extUserAgent string // User-agent request header string
128128
}
129129

130130
// SetBasicAuth enables use of HTTP basic authentication for access
@@ -152,12 +152,71 @@ func (app *App) GetBasicAuthPassword() string {
152152

153153
// SetUserAgentHeader set custom user-agent header for http request
154154
func (app *App) SetUserAgentHeader(userAgentHeader string) {
155-
app.userAgentHeader = userAgentHeader
155+
app.extUserAgent = userAgentHeader
156156
}
157157

158158
// GetUserAgentHeader get user-agent header string
159159
func (app *App) GetUserAgentHeader() string {
160-
return app.userAgentHeader
160+
userAgent := NAME + "/" + VERSION
161+
if len(app.extUserAgent) > 0 {
162+
return userAgent + " " + app.extUserAgent
163+
}
164+
return userAgent
165+
}
166+
167+
func (app *App) createUrl(api string, query string) url.URL {
168+
path := fmt.Sprintf("/k/v1/%s.json", api)
169+
if app.GuestSpaceId > 0 {
170+
path = fmt.Sprintf("/k/guest/%d/v1/%s.json", app.GuestSpaceId, api)
171+
}
172+
173+
resultUrl := url.URL{
174+
Scheme: "https",
175+
Host: app.Domain,
176+
Path: path,
177+
}
178+
179+
if len(query) > 0 {
180+
resultUrl.RawQuery = query
181+
}
182+
return resultUrl
183+
}
184+
func (app *App) setAuth(request *http.Request) {
185+
if app.basicAuth {
186+
request.SetBasicAuth(app.basicAuthUser, app.basicAuthPassword)
187+
}
188+
189+
if len(app.ApiToken) > 0 {
190+
request.Header.Set("X-Cybozu-API-Token", app.ApiToken)
191+
}
192+
193+
if len(app.User) > 0 && len(app.Password) > 0 {
194+
request.Header.Set("X-Cybozu-Authorization", base64.StdEncoding.EncodeToString(
195+
[]byte(app.User+":"+app.Password)))
196+
}
197+
}
198+
199+
//NewRequest create a request connect to kintone api.
200+
func (app *App) NewRequest(method, url string, body io.Reader) (*http.Request, error) {
201+
bodyData := io.Reader(nil)
202+
if body != nil {
203+
bodyData = body
204+
}
205+
206+
request, err := http.NewRequest(method, url, bodyData)
207+
if err != nil {
208+
return nil, err
209+
}
210+
211+
request.Header.Set("User-Agent", app.GetUserAgentHeader())
212+
213+
if method != "GET" {
214+
request.Header.Set("Content-Type", "application/json")
215+
}
216+
217+
app.setAuth(request)
218+
219+
return request, nil
161220
}
162221

163222
func (app *App) newRequest(method, api string, body io.Reader) (*http.Request, error) {
@@ -191,12 +250,8 @@ func (app *App) newRequest(method, api string, body io.Reader) (*http.Request, e
191250
req.Header.Set("X-Cybozu-API-Token", app.ApiToken)
192251
}
193252
req.Header.Set("Content-Type", "application/json")
253+
req.Header.Set("User-Agent", app.GetUserAgentHeader())
194254

195-
if len(app.GetUserAgentHeader()) != 0 {
196-
req.Header.Set("User-Agent", app.userAgentHeader)
197-
} else {
198-
req.Header.Set("User-Agent", NAME+"/"+VERSION)
199-
}
200255
return req, nil
201256
}
202257

@@ -1000,3 +1055,82 @@ func (app *App) Fields() (map[string]*FieldInfo, error) {
10001055
}
10011056
return ret, nil
10021057
}
1058+
1059+
//CreateCursor return the meta data of the Cursor in this application
1060+
func (app *App) CreateCursor(fields []string, query string, size uint64) (*Cursor, error) {
1061+
type cursor struct {
1062+
App uint64 `json:"app"`
1063+
Fields []string `json:"fields"`
1064+
Size uint64 `json:"size"`
1065+
Query string `json:"query"`
1066+
}
1067+
data := cursor{App: app.AppId, Fields: fields, Size: size, Query: query}
1068+
jsonData, _ := json.Marshal(data)
1069+
url := app.createUrl("records/cursor", "")
1070+
request, err := app.NewRequest("POST", url.String(), bytes.NewBuffer(jsonData))
1071+
if err != nil {
1072+
return nil, err
1073+
}
1074+
response, err := app.do(request)
1075+
if err != nil {
1076+
return nil, err
1077+
}
1078+
body, err := parseResponse(response)
1079+
if err != nil {
1080+
return nil, err
1081+
}
1082+
result, err := decodeCursor(body)
1083+
return result, nil
1084+
}
1085+
1086+
// DeleteCursor - Delete cursor by id
1087+
func (app *App) DeleteCursor(id string) error {
1088+
type requestBody struct {
1089+
Id string `json:"id"`
1090+
}
1091+
data, err := json.Marshal(requestBody{Id: id})
1092+
if err != nil {
1093+
return err
1094+
}
1095+
1096+
url := app.createUrl("records/cursor", "")
1097+
request, err := app.NewRequest("DELETE", url.String(), bytes.NewBuffer(data))
1098+
if err != nil {
1099+
return err
1100+
}
1101+
1102+
response, err := app.do(request)
1103+
if err != nil {
1104+
return err
1105+
}
1106+
1107+
_, err = parseResponse(response)
1108+
if err != nil {
1109+
return err
1110+
}
1111+
1112+
return nil
1113+
}
1114+
1115+
//Using Cursor Id to get all records
1116+
//GetRecordsByCursor return the meta data of the Record in this application
1117+
func (app *App) GetRecordsByCursor(id string) (*GetRecordsCursorResponse, error) {
1118+
url := app.createUrl("records/cursor", "id="+id)
1119+
request, err := app.NewRequest("GET", url.String(), nil)
1120+
if err != nil {
1121+
return nil, err
1122+
}
1123+
response, err := app.do(request)
1124+
if err != nil {
1125+
return nil, err
1126+
}
1127+
data, err := parseResponse(response)
1128+
if err != nil {
1129+
return nil, err
1130+
}
1131+
recordsCursorResponse, err := DecodeGetRecordsCursorResponse(data)
1132+
if err != nil {
1133+
return nil, err
1134+
}
1135+
return recordsCursorResponse, nil
1136+
}

app_test.go

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,40 @@ package kintone
66

77
import (
88
"bytes"
9+
"fmt"
910
"io/ioutil"
10-
"os"
1111
"strings"
1212
"testing"
1313
"time"
1414
)
1515

16+
const KINTONE_DOMAIN = "YOUR_DOMAIN"
17+
const KINTONE_USER = "YOUR_KINTONE_USER"
18+
const KINTONE_PASSWORD = "YOUR_KINTONE_PASSWORD"
19+
const KINTONE_API_TOKEN = "YOUR_API_TOKEN"
20+
1621
func newApp(appID uint64) *App {
1722
return &App{
18-
Domain: os.Getenv("KINTONE_DOMAIN"),
19-
User: os.Getenv("KINTONE_USER"),
20-
Password: os.Getenv("KINTONE_PASSWORD"),
23+
Domain: KINTONE_DOMAIN,
24+
User: KINTONE_USER,
25+
Password: KINTONE_PASSWORD,
2126
AppId: appID,
2227
}
2328
}
2429

2530
func newAppWithApiToken(appId uint64) *App {
2631
return &App{
27-
Domain: os.Getenv("KINTONE_DOMAIN"),
28-
ApiToken: os.Getenv("KINTONE_API_TOKEN"),
32+
Domain: KINTONE_DOMAIN,
33+
ApiToken: KINTONE_API_TOKEN,
2934
AppId: appId,
3035
}
3136
}
3237

3338
func newAppInGuestSpace(appId uint64, guestSpaceId uint64) *App {
3439
return &App{
35-
Domain: os.Getenv("KINTONE_DOMAIN"),
36-
User: os.Getenv("KINTONE_USER"),
37-
Password: os.Getenv("KINTONE_PASSWORD"),
40+
Domain: KINTONE_DOMAIN,
41+
User: KINTONE_USER,
42+
Password: KINTONE_PASSWORD,
3843
AppId: appId,
3944
GuestSpaceId: guestSpaceId,
4045
}
@@ -82,7 +87,6 @@ func TestGetRecord(t *testing.T) {
8287
t.Log(len(recs))
8388
}
8489
}
85-
8690
func TestAddRecord(t *testing.T) {
8791
a := newApp(9004)
8892
if len(a.Password) == 0 {
@@ -171,6 +175,58 @@ func TestDeleteRecord(t *testing.T) {
171175
}
172176
}
173177

178+
func TestGetRecordsByCursor(t *testing.T) {
179+
app := newApp(18)
180+
181+
if len(app.Password) == 0 {
182+
t.Skip()
183+
}
184+
185+
cursor := app.createCursorForTest()
186+
record, err := app.GetRecordsByCursor(string(cursor.Id))
187+
188+
if err != nil {
189+
t.Errorf("TestGetCursor is failed: %v", err)
190+
}
191+
fmt.Println(record)
192+
193+
}
194+
195+
func (app *App) createCursorForTest() *Cursor {
196+
cursor, err := app.CreateCursor([]string{"$id", "Status"}, "", 400)
197+
fmt.Println("cursor", cursor)
198+
if err != nil {
199+
fmt.Println("createCursorForTest failed: ", err)
200+
}
201+
return cursor
202+
}
203+
204+
func TestDeleteCursor(t *testing.T) {
205+
app := newApp(18)
206+
if len(app.Password) == 0 {
207+
t.Skip()
208+
}
209+
210+
cursor := app.createCursorForTest()
211+
fmt.Println("cursor", cursor)
212+
err := app.DeleteCursor(string(cursor.Id))
213+
214+
if err != nil {
215+
t.Errorf("TestDeleteCursor is failed: %v", err)
216+
}
217+
}
218+
219+
func TestCreateCursor(t *testing.T) {
220+
app := newApp(18)
221+
if len(app.Password) == 0 {
222+
t.Skip()
223+
}
224+
_, err := app.CreateCursor([]string{"$id", "date"}, "", 100)
225+
if err != nil {
226+
t.Errorf("TestCreateCurSor is failed: %v", err)
227+
}
228+
}
229+
174230
func TestFields(t *testing.T) {
175231
a := newApp(8326)
176232
if len(a.Password) == 0 {

cursor.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package kintone
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
//Object Cursor structure
8+
type Cursor struct {
9+
Id string `json:"id"`
10+
TotalCount string `json:"totalCount"`
11+
}
12+
type GetRecordsCursorResponse struct {
13+
Records []*Record `json:"records"`
14+
Next bool `json:"next"`
15+
}
16+
17+
//decodeCursor decodes JSON response for cursor api
18+
func decodeCursor(b []byte) (c *Cursor, err error) {
19+
err = json.Unmarshal(b, &c)
20+
if err != nil {
21+
return nil, err
22+
}
23+
return c, nil
24+
}
25+
func DecodeGetRecordsCursorResponse(b []byte) (rc *GetRecordsCursorResponse, err error) {
26+
var t struct {
27+
Next bool `json:"next"`
28+
}
29+
err = json.Unmarshal(b, &t)
30+
if err != nil {
31+
return nil, err
32+
}
33+
listRecord, err := DecodeRecords(b)
34+
if err != nil {
35+
return nil, err
36+
}
37+
getRecordsCursorResponse := &GetRecordsCursorResponse{Records: listRecord, Next: t.Next}
38+
return getRecordsCursorResponse, nil
39+
}

cursor_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package kintone
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestDecodeCursor(t *testing.T) {
9+
data := []byte(`{"id":"aaaaaaaaaaaaaaaaaa","totalCount":"null"}`)
10+
cursor, err := decodeCursor(data)
11+
if err != nil {
12+
t.Errorf("TestDecodeCursor is failed: %v", err)
13+
14+
}
15+
fmt.Println(cursor)
16+
}

record.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ func DecodeRecords(b []byte) ([]*Record, error) {
324324
if err != nil {
325325
return nil, errors.New("Invalid JSON format")
326326
}
327+
327328
rec_list := make([]*Record, len(t.Records))
328329
for i, rd := range t.Records {
329330
r, err := decodeRecordData(rd)

0 commit comments

Comments
 (0)