Skip to content

Commit abdfcef

Browse files
committed
as: Add token management API client
1 parent f7f0367 commit abdfcef

3 files changed

Lines changed: 242 additions & 6 deletions

File tree

pkg/applicationserver/io/packages/lora-cloud-device-management-v1/api/client.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
package api
1616

1717
import (
18+
"fmt"
1819
"io"
1920
"net/http"
21+
"net/url"
2022
"path"
2123

2224
"go.thethings.network/lorawan-stack/pkg/version"
@@ -34,20 +36,37 @@ func (f OptionFunc) apply(c *Client) { f(c) }
3436

3537
// Client is an API client for the LoRa Cloud Device Management v1 service.
3638
type Client struct {
37-
token string
39+
token string
40+
client *http.Client
41+
42+
Tokens *Tokens
3843
}
3944

4045
const (
41-
baseURL = "/api/v1"
46+
baseURL = "https://dms.loracloud.com/api/v1"
4247
contentType = "application/json"
4348
)
4449

4550
var (
4651
userAgent = "ttn-lw-application-server/" + version.TTN
4752
)
4853

49-
func (c *Client) newRequest(method, category, entity, operation string, body io.Reader) (*http.Request, error) {
50-
req, err := http.NewRequest(method, path.Join(baseURL, category, entity, operation), body)
54+
type queryParam struct {
55+
key, value string
56+
}
57+
58+
func (c *Client) newRequest(method, category, entity, operation string, body io.Reader, queryParams ...queryParam) (*http.Request, error) {
59+
u, err := url.Parse(baseURL)
60+
if err != nil {
61+
panic(fmt.Sprintf("Failed to parse base URL : %v", err))
62+
}
63+
u.Path = path.Join(u.Path, category, entity, operation)
64+
q := u.Query()
65+
for _, p := range queryParams {
66+
q.Add(p.key, p.value)
67+
}
68+
u.RawQuery = q.Encode()
69+
req, err := http.NewRequest(method, u.String(), body)
5170
if err != nil {
5271
return nil, err
5372
}
@@ -67,8 +86,11 @@ func WithToken(token string) Option {
6786
}
6887

6988
// New creates a new Client with the given options.
70-
func New(opts ...Option) (*Client, error) {
71-
client := &Client{}
89+
func New(cl *http.Client, opts ...Option) (*Client, error) {
90+
client := &Client{
91+
client: cl,
92+
}
93+
client.Tokens = &Tokens{client}
7294
for _, opt := range opts {
7395
opt.apply(client)
7496
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright © 2019 The Things Network Foundation, The Things Industries B.V.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package api
16+
17+
import (
18+
"encoding/json"
19+
"io"
20+
21+
"github.com/golang/protobuf/proto"
22+
"go.thethings.network/lorawan-stack/pkg/applicationserver/io/packages/lora-cloud-device-management-v1/api/objects"
23+
"go.thethings.network/lorawan-stack/pkg/errors"
24+
)
25+
26+
const (
27+
listOperation = "list"
28+
updateOperation = "update"
29+
addOperation = "add"
30+
)
31+
32+
type apiErrorDetail string
33+
34+
func (s *apiErrorDetail) Reset() { *s = "" }
35+
func (s apiErrorDetail) String() string { return string(s) }
36+
func (apiErrorDetail) ProtoMessage() {}
37+
38+
type baseResponse struct {
39+
Result interface{} `json:"result"`
40+
Errors []string `json:"errors"`
41+
}
42+
43+
var errAPICallFailed = errors.Define("api_call_failed", "", "")
44+
45+
func parse(result interface{}, body io.Reader) error {
46+
r := &baseResponse{
47+
Result: result,
48+
}
49+
err := json.NewDecoder(body).Decode(r)
50+
if err != nil {
51+
return err
52+
}
53+
if len(r.Errors) == 0 {
54+
return nil
55+
}
56+
var details []proto.Message
57+
for _, message := range r.Errors {
58+
ed := apiErrorDetail(message)
59+
details = append(details, &ed)
60+
}
61+
return errAPICallFailed.WithDetails(details...)
62+
}
63+
64+
type tokensListResponse struct {
65+
Tokens []objects.TokenInfo `json:"tokens"`
66+
}
67+
68+
type tokenAddRequest struct {
69+
Name string `json:"name"`
70+
Capabilities []string `json:"capabilities"`
71+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright © 2019 The Things Network Foundation, The Things Industries B.V.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package api
16+
17+
import (
18+
"bytes"
19+
"encoding/json"
20+
"net/http"
21+
22+
"go.thethings.network/lorawan-stack/pkg/applicationserver/io/packages/lora-cloud-device-management-v1/api/objects"
23+
)
24+
25+
// Tokens is an API client for the Token Management API.
26+
type Tokens struct {
27+
*Client
28+
}
29+
30+
const (
31+
tokenEntity = "token"
32+
nameParam = "name"
33+
renewParam = "renew"
34+
)
35+
36+
// List returns the tokens.
37+
func (t *Tokens) List() ([]objects.TokenInfo, error) {
38+
req, err := t.newRequest(http.MethodGet, tokenEntity, "", listOperation, nil)
39+
if err != nil {
40+
return nil, err
41+
}
42+
resp, err := t.client.Do(req)
43+
if err != nil {
44+
return nil, err
45+
}
46+
defer resp.Body.Close()
47+
var response tokensListResponse
48+
err = parse(&response, resp.Body)
49+
if err != nil {
50+
return nil, err
51+
}
52+
return response.Tokens, nil
53+
}
54+
55+
// Update changes the name of the given token and optionally regenerates it.
56+
func (t *Tokens) Update(token, newName string, renew bool) (*objects.TokenInfo, error) {
57+
var params []queryParam
58+
if newName != "" {
59+
params = append(params, queryParam{nameParam, newName})
60+
}
61+
if renew {
62+
params = append(params, queryParam{renewParam, ""})
63+
}
64+
req, err := t.newRequest(http.MethodPut, tokenEntity, token, updateOperation, nil, params...)
65+
if err != nil {
66+
return nil, err
67+
}
68+
resp, err := t.client.Do(req)
69+
if err != nil {
70+
return nil, err
71+
}
72+
defer resp.Body.Close()
73+
var response objects.TokenInfo
74+
err = parse(&response, resp.Body)
75+
if err != nil {
76+
return nil, err
77+
}
78+
return &response, nil
79+
}
80+
81+
// Add creates a token with the given name and capabilities.
82+
func (t *Tokens) Add(name string, capabilities ...string) (*objects.TokenInfo, error) {
83+
buffer := bytes.NewBuffer(nil)
84+
err := json.NewEncoder(buffer).Encode(&tokenAddRequest{
85+
Name: name,
86+
Capabilities: capabilities,
87+
})
88+
if err != nil {
89+
return nil, err
90+
}
91+
req, err := t.newRequest(http.MethodPost, tokenEntity, "", addOperation, buffer)
92+
if err != nil {
93+
return nil, err
94+
}
95+
resp, err := t.client.Do(req)
96+
if err != nil {
97+
return nil, err
98+
}
99+
defer resp.Body.Close()
100+
var response objects.TokenInfo
101+
err = parse(&response, resp.Body)
102+
if err != nil {
103+
return nil, err
104+
}
105+
return &response, nil
106+
}
107+
108+
// Remove removes the given token.
109+
func (t *Tokens) Remove(token string) error {
110+
req, err := t.newRequest(http.MethodDelete, tokenEntity, token, "", nil)
111+
if err != nil {
112+
return err
113+
}
114+
resp, err := t.client.Do(req)
115+
if err != nil {
116+
return err
117+
}
118+
defer resp.Body.Close()
119+
err = parse(nil, resp.Body)
120+
if err != nil {
121+
return err
122+
}
123+
return nil
124+
}
125+
126+
// Get returns the contents of the given token.
127+
func (t *Tokens) Get(token string) (*objects.TokenInfo, error) {
128+
req, err := t.newRequest(http.MethodGet, tokenEntity, token, "", nil)
129+
if err != nil {
130+
return nil, err
131+
}
132+
resp, err := t.client.Do(req)
133+
if err != nil {
134+
return nil, err
135+
}
136+
defer resp.Body.Close()
137+
var response objects.TokenInfo
138+
err = parse(&response, resp.Body)
139+
if err != nil {
140+
return nil, err
141+
}
142+
return &response, nil
143+
}

0 commit comments

Comments
 (0)