• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

m-lab / autojoin / 13975345119

20 Mar 2025 05:13PM UTC coverage: 92.629% (-1.2%) from 93.786%
13975345119

Pull #68

github

robertodauria
Restore Cloud API keys creation, set datastore key to same value
Pull Request #68: Restore Cloud API keys creation, set datastore key to same value

32 of 43 new or added lines in 3 files covered. (74.42%)

19 existing lines in 2 files now uncovered.

1307 of 1411 relevant lines covered (92.63%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.81
/internal/adminx/datastore.go
1
package adminx
2

3
import (
4
        "context"
5
        "crypto/rand"
6
        "encoding/base64"
7
        "errors"
8
        "time"
9

10
        "cloud.google.com/go/datastore"
11
)
12

13
const autojoinNamespace = "autojoin"
14
const OrgKind = "Organization"
15
const APIKeyKind = "APIKey"
16

17
var (
18
        // ErrInvalidKey is returned when the API key is not found in Datastore
19
        ErrInvalidKey = errors.New("invalid API key")
20
)
21

22
// DatastoreClient is an interface for interacting with Datastore.
23
type DatastoreClient interface {
24
        Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error)
25
        Get(ctx context.Context, key *datastore.Key, dst interface{}) error
26
        GetAll(ctx context.Context, q *datastore.Query, dst interface{}) ([]*datastore.Key, error)
27
}
28

29
// Organization represents a Datastore entity for storing organization metadata.
30
type Organization struct {
31
        Name                  string    `datastore:"name"`
32
        Email                 string    `datastore:"email"`
33
        CreatedAt             time.Time `datastore:"created_at"`
34
        ProbabilityMultiplier float64   `datastore:"probability_multiplier"`
35
}
36

37
// APIKey represents a Datastore entity for storing API key metadata.
38
type APIKey struct {
39
        CreatedAt time.Time `datastore:"created_at"`
40
        Key       string    `datastore:"key"`
41
}
42

43
// DatastoreOrgManager maintains state for managing organizations and API keys in Datastore.
44
type DatastoreOrgManager struct {
45
        client    DatastoreClient
46
        project   string
47
        namespace string
48
}
49

50
// NewDatastoreManager creates a new DatastoreOrgManager instance.
51
func NewDatastoreManager(client DatastoreClient, project string) *DatastoreOrgManager {
1✔
52
        return &DatastoreOrgManager{
1✔
53
                client:    client,
1✔
54
                project:   project,
1✔
55
                namespace: autojoinNamespace,
1✔
56
        }
1✔
57
}
1✔
58

59
// CreateOrganization creates a new organization entity in Datastore.
60
func (d *DatastoreOrgManager) CreateOrganization(ctx context.Context, name, email string) error {
×
61
        key := datastore.NameKey(OrgKind, name, nil)
×
62
        key.Namespace = d.namespace
×
63

×
64
        org := &Organization{
×
65
                Name:                  name,
×
66
                Email:                 email,
×
67
                CreatedAt:             time.Now().UTC(),
×
68
                ProbabilityMultiplier: 1.0,
×
69
        }
×
70

×
71
        _, err := d.client.Put(ctx, key, org)
×
72
        return err
×
73
}
×
74

75
// GetOrganization retrieves an organization by its name.
76
func (d *DatastoreOrgManager) GetOrganization(ctx context.Context, orgName string) (*Organization, error) {
×
77
        key := datastore.NameKey(OrgKind, orgName, nil)
×
78
        key.Namespace = d.namespace
×
79

×
80
        var org Organization
×
81
        err := d.client.Get(ctx, key, &org)
×
82
        if err != nil {
×
83
                return nil, err
×
84
        }
×
85

86
        return &org, nil
×
87
}
88

89
// CreateAPIKeyWithValue creates a new API key as a child entity of the organization.
NEW
90
func (d *DatastoreOrgManager) CreateAPIKeyWithValue(ctx context.Context, org, value string) (string, error) {
×
UNCOV
91
        parentKey := datastore.NameKey(OrgKind, org, nil)
×
UNCOV
92
        parentKey.Namespace = d.namespace
×
UNCOV
93

×
UNCOV
94
        // Use the generated string as the key name
×
NEW
95
        key := datastore.NameKey(APIKeyKind, value, parentKey)
×
UNCOV
96
        key.Namespace = d.namespace
×
UNCOV
97

×
UNCOV
98
        apiKey := &APIKey{
×
UNCOV
99
                CreatedAt: time.Now().UTC(),
×
NEW
100
                Key:       value,
×
UNCOV
101
        }
×
UNCOV
102

×
NEW
103
        resKey, err := d.client.Put(ctx, key, apiKey)
×
UNCOV
104
        if err != nil {
×
UNCOV
105
                return "", err
×
UNCOV
106
        }
×
107

NEW
108
        return resKey.Name, nil
×
109
}
110

111
// GetAPIKeys retrieves all API keys for an organization
112
func (d *DatastoreOrgManager) GetAPIKeys(ctx context.Context, org string) ([]string, error) {
×
113
        parentKey := datastore.NameKey(OrgKind, org, nil)
×
114
        parentKey.Namespace = d.namespace
×
115

×
NEW
116
        q := datastore.NewQuery(APIKeyKind).
×
NEW
117
                Namespace(d.namespace).
×
NEW
118
                Ancestor(parentKey).
×
NEW
119
                KeysOnly()
×
120

×
121
        keys, err := d.client.GetAll(ctx, q, nil)
×
122
        if err != nil {
×
123
                return nil, err
×
124
        }
×
125

126
        apiKeys := make([]string, len(keys))
×
127
        for i, key := range keys {
×
128
                apiKeys[i] = key.Name
×
129
        }
×
130

131
        return apiKeys, nil
×
132
}
133

134
// ValidateKey checks if the API key exists and returns the associated organization name.
135
func (d *DatastoreOrgManager) ValidateKey(ctx context.Context, key string) (string, error) {
1✔
136
        q := datastore.NewQuery(APIKeyKind).
1✔
137
                Namespace(d.namespace).
1✔
138
                FilterField("key", "=", key).Limit(1)
1✔
139

1✔
140
        var keys []*datastore.Key
1✔
141
        var entities []APIKey
1✔
142
        keys, err := d.client.GetAll(ctx, q, &entities)
1✔
143
        if err != nil {
2✔
144
                return "", err
1✔
145
        }
1✔
146
        if len(keys) == 0 {
2✔
147
                return "", ErrInvalidKey
1✔
148
        }
1✔
149

150
        // Get the parent (organization) key from the first result
151
        orgKey := keys[0].Parent
1✔
152
        if orgKey == nil {
1✔
153
                return "", errors.New("API key has no parent organization")
×
154
        }
×
155

156
        return orgKey.Name, nil
1✔
157
}
158

159
// GenerateAPIKey generates a random string to be used as API key.
UNCOV
160
func GenerateAPIKey() (string, error) {
×
UNCOV
161
        b := make([]byte, 32) // 256 bits of randomness
×
UNCOV
162
        _, err := rand.Read(b)
×
UNCOV
163
        if err != nil {
×
164
                return "", err
×
165
        }
×
UNCOV
166
        return base64.RawURLEncoding.EncodeToString(b), nil
×
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc