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

Add support for Remote Config #396

Merged
merged 3 commits into from
Oct 21, 2020
Merged
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
10 changes: 10 additions & 0 deletions firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"firebase.google.com/go/v4/iid"
"firebase.google.com/go/v4/internal"
"firebase.google.com/go/v4/messaging"
"firebase.google.com/go/v4/remoteconfig"
"firebase.google.com/go/v4/storage"
"google.golang.org/api/option"
"google.golang.org/api/transport"
Expand Down Expand Up @@ -128,6 +129,15 @@ func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
return messaging.NewClient(ctx, conf)
}

// RemoteConfig returns an instance of remoteconfig.Client.
func (a *App) RemoteConfig(ctx context.Context) (*remoteconfig.Client, error) {
conf := &internal.RemoteConfig{
ProjectID: a.projectID,
Opts: a.opts,
}
return remoteconfig.NewClient(ctx, conf)
}

// NewApp creates a new App from the provided config and client options.
//
// If the client options contain a valid credential (a service account file, a refresh token
Expand Down
6 changes: 6 additions & 0 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ type MessagingConfig struct {
Version string
}

// RemoteConfig represents the configuration of Firebase Cloud Remote Config service.
type RemoteConfig struct {
Opts []option.ClientOption
ProjectID string
}

// MockTokenSource is a TokenSource implementation that can be used for testing.
type MockTokenSource struct {
AccessToken string
Expand Down
219 changes: 219 additions & 0 deletions remoteconfig/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package remoteconfig

import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"

"firebase.google.com/go/v4/internal"
)

const (
defaultRemoteConfigEndpoint = "https://firebaseremoteconfig.googleapis.com/v1"
)

// Client .
type Client struct {
hc *internal.HTTPClient
projectID string
}

func (c *Client) getRootURL() string {
return fmt.Sprintf("%s/projects/%s/remoteConfig", defaultRemoteConfigEndpoint, c.projectID)
}

// GetRemoteConfig https://firebase.google.com/docs/reference/remote-config/rest/v1/projects/getRemoteConfig
func (c *Client) GetRemoteConfig(versionNumber string) (*Response, error) {
lahirumaramba marked this conversation as resolved.
Show resolved Hide resolved
var opts []internal.HTTPOption

// Optional. Version number of the RemoteConfig to look up.
// If not specified, the latest RemoteConfig will be returned.
if versionNumber != "" {
opts = append(opts, internal.WithQueryParam("versionNumber", versionNumber))
}

var data RemoteConfig

resp, err := c.hc.DoAndUnmarshal(
context.Background(),
&internal.Request{
Method: http.MethodGet,
URL: c.getRootURL(),
Opts: opts,
},
&data,
)
if err != nil {
return nil, err
}

result := &Response{
RemoteConfig: data,
Etag: resp.Header.Get("Etag"),
}

return result, nil
}

// UpdateRemoteConfig https://firebase.google.com/docs/reference/remote-config/rest/v1/projects/updateRemoteConfig
func (c *Client) UpdateRemoteConfig(eTag string, validateOnly bool) (*Response, error) {
lahirumaramba marked this conversation as resolved.
Show resolved Hide resolved
if eTag == "" {
eTag = "*"
}

var opts []internal.HTTPOption
opts = append(opts, internal.WithHeader("If-Match", eTag))
opts = append(opts, internal.WithQueryParam("validateOnly", strconv.FormatBool(validateOnly)))

var data RemoteConfig

resp, err := c.hc.DoAndUnmarshal(
context.Background(),
&internal.Request{
Method: http.MethodPut,
URL: c.getRootURL(),
Opts: opts,
},
&data,
)
if err != nil {
return nil, err
}

result := &Response{
RemoteConfig: data,
Etag: resp.Header.Get("Etag"),
}

return result, nil
}

// ListVersions https://firebase.google.com/docs/reference/remote-config/rest/v1/projects.remoteConfig/listVersions
func (c *Client) ListVersions(options *ListVersionsOptions) (*ListVersionsResponse, error) {
lahirumaramba marked this conversation as resolved.
Show resolved Hide resolved
var opts []internal.HTTPOption
if options.PageSize != 0 {
opts = append(opts, internal.WithQueryParam("pageSize", strconv.Itoa(options.PageSize)))
}

if options.PageToken != "" {
opts = append(opts, internal.WithQueryParam("pageToken", options.PageToken))
}

if options.EndVersionNumber != "" {
opts = append(opts, internal.WithQueryParam("endVersionNumber", options.EndVersionNumber))
}

if !options.StartTime.IsZero() {
opts = append(opts, internal.WithQueryParam("startTime", options.StartTime.Format(time.RFC3339Nano)))
}

if !options.EndTime.IsZero() {
opts = append(opts, internal.WithQueryParam("endTime", options.EndTime.Format(time.RFC3339Nano)))
}

var data ListVersionsResponse
url := fmt.Sprintf("%s:listVersions", c.getRootURL())

_, err := c.hc.DoAndUnmarshal(
context.Background(),
&internal.Request{
Method: http.MethodGet,
URL: url,
Opts: opts,
},
&data,
)
if err != nil {
return nil, err
}

return &data, nil
}

// Rollback will perform a rollback operation on the template
// https://firebase.google.com/docs/reference/remote-config/rest/v1/projects.remoteConfig/rollback
func (c *Client) Rollback(ctx context.Context, versionNumber string) (*Template, error) {
if versionNumber == "" {
return nil, errors.New("versionNumber is required to rollback a Remote Config template")
}

var data Template
url := fmt.Sprintf("%s:rollback", c.getRootURL())

_, err := c.hc.DoAndUnmarshal(
context.Background(),
&internal.Request{
Method: http.MethodPost,
Body: internal.NewJSONEntity(
struct {
VersionNumber string `json:"versionNumber"`
}{
VersionNumber: versionNumber,
},
),
URL: url,
},
&data,
)
if err != nil {
return nil, err
}

return &data, nil
}

// NewClient returns the default remote config
func NewClient(ctx context.Context, c *internal.RemoteConfig) (*Client, error) {
if c.ProjectID == "" {
return nil, errors.New("project ID is required to access Firebase Cloud Remote Config client")
}

hc, _, err := internal.NewHTTPClient(ctx, c.Opts...)
if err != nil {
return nil, err
}

return &Client{
hc: hc,
projectID: c.ProjectID,
}, nil
}

// GetTemplate will retrieve the latest template
func (c *Client) GetTemplate(ctx context.Context) (*Template, error) {
// TODO
return nil, nil
}

// GetTemplateAtVersion will retrieve the specified version of the template
func (c *Client) GetTemplateAtVersion(ctx context.Context, versionNumber string) (*Template, error) {
// TODO
return nil, nil
}

// Versions will list the versions of the template
func (c *Client) Versions(ctx context.Context, options *ListVersionsOptions) (*VersionIterator, error) {
// TODO
return nil, nil
}

// PublishTemplate will publish the specified temnplate
func (c *Client) PublishTemplate(ctx context.Context, template *Template) (*Template, error) {
// TODO
return nil, nil
}

// ValidateTemplate will run validations for the current template
func (c *Client) ValidateTemplate(ctx context.Context, template *Template) (*Template, error) {
// TODO
return nil, nil
}

// ForcePublishTemplate will publish the template irrespective of the outcome from validations
func (c *Client) ForcePublishTemplate(ctx context.Context, template *Template) (*Template, error) {
lahirumaramba marked this conversation as resolved.
Show resolved Hide resolved
// TODO
return nil, nil
}
3 changes: 3 additions & 0 deletions remoteconfig/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Reference RemoteConfig using the REST API implementation

package remoteconfig
Loading