This repository has been archived by the owner on Apr 5, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
database.go
430 lines (357 loc) · 10.1 KB
/
database.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
// Package firebase provides Firebase v3+ compatible clients.
package firebase
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"golang.org/x/oauth2"
)
const (
// DefaultWatchBuffer is the default length of an event channel created on
// a call to Watch.
DefaultWatchBuffer = 64
)
// OpType is the Firebase operation type.
type OpType string
const (
// OpTypeGet is the Firebase Push operation.
OpTypeGet OpType = "GET"
// OpTypePush is the Firebase Push operation.
OpTypePush OpType = "POST"
// OpTypeSet is the Firebase Set operation.
OpTypeSet OpType = "PUT"
// OpTypeUpdate is the Firebase Update operation.
OpTypeUpdate OpType = "PATCH"
// OpTypeRemove is the Firebase Remove operation.
OpTypeRemove OpType = "DELETE"
)
// Do executes an HTTP operation on Firebase database ref r passing the
// supplied value v as JSON marshaled data and decoding the response to d.
func Do(op OpType, r *DatabaseRef, v, d interface{}, opts ...QueryOption) error {
var err error
// encode v
var body io.Reader
switch x := v.(type) {
case io.Reader:
body = x
case []byte:
body = bytes.NewReader(x)
default:
if v != nil {
buf, err := json.Marshal(v)
if err != nil {
return &Error{
Err: fmt.Sprintf("could not marshal json: %v", err),
}
}
body = bytes.NewReader(buf)
}
}
// create client and request
client, req, err := r.clientAndRequest(string(op), body, opts...)
if err != nil {
return err
}
// execute
res, err := client.Do(req)
if err != nil {
return &Error{
Err: fmt.Sprintf("could not execute request: %v", err),
}
}
defer res.Body.Close()
// check for server error
err = checkServerError(res)
if err != nil {
return err
}
// decode body to d
if d != nil {
dec := json.NewDecoder(res.Body)
dec.UseNumber()
err = dec.Decode(d)
if err != nil {
return &Error{
Err: fmt.Sprintf("could not unmarshal json: %v", err),
}
}
}
return nil
}
// Get retrieves the values stored at Firebase database ref r and decodes them
// into d.
func Get(r *DatabaseRef, d interface{}, opts ...QueryOption) error {
return Do(OpTypeGet, r, nil, d, opts...)
}
// Set stores values v at Firebase database ref r.
func Set(r *DatabaseRef, v interface{}, opts ...QueryOption) error {
return Do(OpTypeSet, r, v, nil, opts...)
}
// Push pushes values v to Firebase database ref r, returning the name (ID) of
// the pushed node.
func Push(r *DatabaseRef, v interface{}, opts ...QueryOption) (string, error) {
var res struct {
Name string `json:"name"`
}
err := Do(OpTypePush, r, v, &res, opts...)
if err != nil {
return "", err
}
return res.Name, nil
}
// Update updates the values stored at Firebase database ref r to v.
func Update(r *DatabaseRef, v interface{}, opts ...QueryOption) error {
return Do(OpTypeUpdate, r, v, nil, opts...)
}
// Remove removes the values stored at Firebase database ref r.
func Remove(r *DatabaseRef, opts ...QueryOption) error {
return Do(OpTypeRemove, r, nil, nil, opts...)
}
// SetRules sets the security rules for Firebase database ref r.
func SetRules(r *DatabaseRef, v interface{}) error {
return Do(OpTypeSet, r.Ref("/.settings/rules"), v, nil)
}
// SetRulesJSON sets the JSON-encoded security rules for Firebase database ref
// r.
func SetRulesJSON(r *DatabaseRef, buf []byte) error {
var err error
var v interface{}
// decode
d := json.NewDecoder(bytes.NewReader(buf))
d.UseNumber()
err = d.Decode(&v)
if err != nil {
return &Error{
Err: fmt.Sprintf("could not decode json: %v", err),
}
}
// encode
var rules bytes.Buffer
e := json.NewEncoder(&rules)
e.SetEscapeHTML(false)
e.SetIndent("", " ")
err = e.Encode(&v)
if err != nil {
return &Error{
Err: fmt.Sprintf("could not encode json: %v", err),
}
}
return Do(OpTypeSet, r.Ref("/.settings/rules"), rules.Bytes(), nil)
}
// GetRulesJSON retrieves the security rules for Firebase database ref r.
func GetRulesJSON(r *DatabaseRef) ([]byte, error) {
var d json.RawMessage
err := Do(OpTypeGet, r.Ref("/.settings/rules"), nil, &d)
if err != nil {
return nil, err
}
return []byte(d), nil
}
// DatabaseRef is a Firebase database reference.
type DatabaseRef struct {
rw sync.RWMutex
url *url.URL
transport http.RoundTripper
// source is the oauth2 token source.
source oauth2.TokenSource
queryOpts []QueryOption
watchBufLen int
}
// NewDatabaseRef creates a new Firebase base database ref using the supplied
// options.
func NewDatabaseRef(opts ...Option) (*DatabaseRef, error) {
var err error
// create client
r := &DatabaseRef{
watchBufLen: DefaultWatchBuffer,
}
// apply opts
for _, o := range opts {
err = o(r)
if err != nil {
return nil, err
}
}
// check url was set
if r.url == nil {
return nil, errors.New("no firebase url specified")
}
return r, nil
}
// httpClient returns a http.Client suitable for use with Firebase.
func (r *DatabaseRef) httpClient() (*http.Client, error) {
r.rw.RLock()
defer r.rw.RUnlock()
transport := r.transport
// set oauth2 transport
if r.source != nil {
transport = &oauth2.Transport{
Source: r.source,
Base: transport,
}
}
return &http.Client{
Transport: transport,
}, nil
}
// createRequest creates a http.Request for the Firebase database ref with
// method, body, and query opts.
func (r *DatabaseRef) createRequest(method string, body io.Reader, opts ...QueryOption) (*http.Request, error) {
var err error
// build url
u := r.URL().String() + ".json"
if len(r.queryOpts) > 0 {
opts = append(r.queryOpts, opts...)
}
// build query params
if len(opts) > 0 {
v := make(url.Values)
for _, o := range opts {
err = o(v)
if err != nil {
return nil, err
}
}
if vstr := v.Encode(); vstr != "" {
u = u + "?" + vstr
}
}
// create request
req, err := http.NewRequest(method, u, body)
if err != nil {
return nil, err
}
// substitute + on raw path
if strings.Contains(req.URL.Path, "+") {
req.URL.RawPath = strings.Replace(req.URL.Path, "+", "%2B", -1)
}
return req, nil
}
// clientAndRequest creates a *http.Client and *http.Request for the Firebase
// ref.
func (r *DatabaseRef) clientAndRequest(method string, body io.Reader, opts ...QueryOption) (*http.Client, *http.Request, error) {
var err error
// get client
client, err := r.httpClient()
if err != nil {
return nil, nil, &Error{
Err: fmt.Sprintf("could not create client: %v", err),
}
}
// create request
req, err := r.createRequest(method, body, opts...)
if err != nil {
return nil, nil, &Error{
Err: fmt.Sprintf("could not create request: %v", err),
}
}
return client, req, nil
}
// Ref creates a new Firebase database child ref, locked to the specified path.
//
// NOTE: any Option passed returning an error will cause this func to panic.
// Instead if an Option might return an error, then it should be applied after
// the child ref has been created in the following manner:
//
// child := db.Ref("/path/to/child")
// err := SomeOption(child)
// if err != nil { log.Fatal(err) }
func (r *DatabaseRef) Ref(path string, opts ...Option) *DatabaseRef {
r.rw.RLock()
defer r.rw.RUnlock()
// create new path
curpath := r.url.Path
if !strings.HasSuffix(curpath, "/") {
curpath += "/"
}
path = strings.TrimPrefix(path, "/")
// create child ref
c := &DatabaseRef{
url: &url.URL{
Scheme: r.url.Scheme,
Opaque: r.url.Opaque,
User: r.url.User,
Host: r.url.Host,
Path: curpath + path,
},
transport: r.transport,
source: r.source,
queryOpts: r.queryOpts,
watchBufLen: r.watchBufLen,
}
// apply opts
for _, o := range opts {
err := o(c)
if err != nil {
// options that could error out should not be applied here
panic(err)
}
}
return c
}
// URL returns the URL for the Firebase database ref.
func (r *DatabaseRef) URL() *url.URL {
return r.url
}
// Get retrieves the values stored at the Firebase database ref and decodes
// them into d.
func (r *DatabaseRef) Get(d interface{}, opts ...QueryOption) error {
return Get(r, d, opts...)
}
// Set stores values v at the Firebase database ref.
func (r *DatabaseRef) Set(v interface{}, opts ...QueryOption) error {
return Set(r, v, opts...)
}
// Push pushes values v to the Firebase database ref, returning the name (ID)
// of the pushed node.
func (r *DatabaseRef) Push(v interface{}, opts ...QueryOption) (string, error) {
return Push(r, v, opts...)
}
// Update updates the values stored at the Firebase database ref to v.
func (r *DatabaseRef) Update(v interface{}, opts ...QueryOption) error {
return Update(r, v, opts...)
}
// Remove removes the values stored at the Firebase database ref.
func (r *DatabaseRef) Remove(opts ...QueryOption) error {
return Remove(r, opts...)
}
// SetRules sets the security rules for the Firebase database ref.
func (r *DatabaseRef) SetRules(v interface{}) error {
return SetRules(r, v)
}
// SetRulesJSON sets the JSON-encoded security rules for the Firebase database
// ref.
func (r *DatabaseRef) SetRulesJSON(buf []byte) error {
return SetRulesJSON(r, buf)
}
// GetRulesJSON retrieves the security rules for the Firebase database ref.
func (r *DatabaseRef) GetRulesJSON() ([]byte, error) {
return GetRulesJSON(r)
}
// Watch watches the Firebase database ref for events, emitting encountered
// events on the returned channel. Watch ends when the passed context is done,
// when the remote connection is closed, or when an error is encountered while
// reading events from the server.
//
// NOTE: the Log option will not work with Watch/Listen.
func (r *DatabaseRef) Watch(ctxt context.Context, opts ...QueryOption) (<-chan *Event, error) {
return Watch(r, ctxt, opts...)
}
// Listen listens on the Firebase database ref for any of the the specified
// eventTypes, emitting them on the returned channel.
//
// The returned channel is closed only when the context is done. If the
// Firebase connection closes, or the auth token is revoked, then Listen will
// continue to reattempt connecting to the Firebase database ref.
//
// NOTE: the Log option will not work with Watch/Listen.
func (r *DatabaseRef) Listen(ctxt context.Context, eventTypes []EventType, opts ...QueryOption) <-chan *Event {
return Listen(r, ctxt, eventTypes, opts...)
}