ducky/devices
2015-11-12
Child:600326d50e74
ducky/devices/devices.go
Initial attempt. Create the basic types (Device, DeviceType, DeviceChange) that we'll be using in the service. Also, create our Storer interface, and the helper methods to store, retrieve, and update information in the datastore. Also, we're using Godep, so check in our Godeps folder and the vendor folder. Note that this only works on Go 1.5 and later, with the GO15VENDOREXPERIMENT environment variable set to 1.
| paddy@0 | 1 package devices |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@0 | 4 "errors" |
| paddy@0 | 5 "log" |
| paddy@0 | 6 "time" |
| paddy@0 | 7 |
| paddy@0 | 8 "golang.org/x/net/context" |
| paddy@0 | 9 |
| paddy@0 | 10 "code.secondbit.org/uuid.hg" |
| paddy@0 | 11 ) |
| paddy@0 | 12 |
| paddy@0 | 13 var ( |
| paddy@0 | 14 // ErrDeviceNotFound is returned when the specified device couldn't be found. |
| paddy@0 | 15 ErrDeviceNotFound = errors.New("device not found") |
| paddy@0 | 16 ) |
| paddy@0 | 17 |
| paddy@0 | 18 // Device represents a specific device that updates can be pushed to. |
| paddy@0 | 19 type Device struct { |
| paddy@0 | 20 ID uuid.ID |
| paddy@0 | 21 Name string |
| paddy@0 | 22 Owner uuid.ID |
| paddy@0 | 23 Type DeviceType |
| paddy@0 | 24 Created time.Time |
| paddy@0 | 25 LastSeen time.Time |
| paddy@0 | 26 PushToken string |
| paddy@0 | 27 } |
| paddy@0 | 28 |
| paddy@0 | 29 // ApplyChange returns a Device that is a copy of the passed Device, |
| paddy@0 | 30 // but with the passed DeviceChange applied. |
| paddy@0 | 31 func ApplyChange(d Device, change DeviceChange) Device { |
| paddy@0 | 32 result := d |
| paddy@0 | 33 if change.Name != nil { |
| paddy@0 | 34 result.Name = *change.Name |
| paddy@0 | 35 } |
| paddy@0 | 36 if change.Owner != nil { |
| paddy@0 | 37 result.Owner = *change.Owner |
| paddy@0 | 38 } else { |
| paddy@0 | 39 // We don't want to accidentally leave a slice that |
| paddy@0 | 40 // is owned by both behind. |
| paddy@0 | 41 result.Owner = d.Owner.Copy() |
| paddy@0 | 42 } |
| paddy@0 | 43 if change.Type != nil { |
| paddy@0 | 44 result.Type = *change.Type |
| paddy@0 | 45 } |
| paddy@0 | 46 if change.Created != nil { |
| paddy@0 | 47 result.Created = *change.Created |
| paddy@0 | 48 } |
| paddy@0 | 49 if change.LastSeen != nil { |
| paddy@0 | 50 result.LastSeen = *change.LastSeen |
| paddy@0 | 51 } |
| paddy@0 | 52 if change.PushToken != nil { |
| paddy@0 | 53 result.PushToken = *change.PushToken |
| paddy@0 | 54 } |
| paddy@0 | 55 return result |
| paddy@0 | 56 } |
| paddy@0 | 57 |
| paddy@0 | 58 // DeviceType is an enum specifying which type of Device it is. Usually, |
| paddy@0 | 59 // this is something like `android_phone` or `android_tablet`. |
| paddy@0 | 60 type DeviceType string |
| paddy@0 | 61 |
| paddy@0 | 62 // DeviceChange represents a set of changes to a Device that will be used |
| paddy@0 | 63 // to update a Device. |
| paddy@0 | 64 type DeviceChange struct { |
| paddy@0 | 65 DeviceID uuid.ID |
| paddy@0 | 66 Name *string |
| paddy@0 | 67 Owner *uuid.ID |
| paddy@0 | 68 Type *DeviceType |
| paddy@0 | 69 Created *time.Time |
| paddy@0 | 70 LastSeen *time.Time |
| paddy@0 | 71 PushToken *string |
| paddy@0 | 72 } |
| paddy@0 | 73 |
| paddy@0 | 74 // Storer is an interface to control how data is stored in and retrieved from |
| paddy@0 | 75 // the datastore. |
| paddy@0 | 76 type Storer interface { |
| paddy@0 | 77 GetDevices(ids []uuid.ID, c context.Context) (map[string]Device, error) |
| paddy@0 | 78 UpdateDevice(change DeviceChange, c context.Context) error |
| paddy@0 | 79 DeleteDevices(ids []uuid.ID, c context.Context) error |
| paddy@0 | 80 CreateDevices(devices []Device, c context.Context) error |
| paddy@0 | 81 ListDevicesByOwner(user uuid.ID, c context.Context) ([]Device, error) |
| paddy@0 | 82 |
| paddy@0 | 83 // These are used for testing only. |
| paddy@0 | 84 Factory(c context.Context) (Storer, error) |
| paddy@0 | 85 Destroy(c context.Context) error |
| paddy@0 | 86 } |
| paddy@0 | 87 |
| paddy@0 | 88 // GetMany returns as many of the Devices specified by the passed IDs as possible. |
| paddy@0 | 89 // They are returned as a map, with the key being the string version of the ID. |
| paddy@0 | 90 // No error will be returned if a Device can't be found. |
| paddy@0 | 91 func GetMany(ids []uuid.ID, c context.Context) (map[string]Device, error) { |
| paddy@0 | 92 results := map[string]Device{} |
| paddy@0 | 93 storer, err := getStorer(c) |
| paddy@0 | 94 if err != nil { |
| paddy@0 | 95 log.Printf("Error retrieving Storer: %+v\n", err) |
| paddy@0 | 96 return results, err |
| paddy@0 | 97 } |
| paddy@0 | 98 results, err = storer.GetDevices(ids, c) |
| paddy@0 | 99 if err != nil { |
| paddy@0 | 100 log.Printf("Error retrieving Devices from %T: %+v\n", storer, err) |
| paddy@0 | 101 return results, err |
| paddy@0 | 102 } |
| paddy@0 | 103 return results, nil |
| paddy@0 | 104 } |
| paddy@0 | 105 |
| paddy@0 | 106 // Get returns the Device specified by the passed ID. If the Device can't be found, |
| paddy@0 | 107 // an ErrDeviceNotFound error is returned. |
| paddy@0 | 108 func Get(id uuid.ID, c context.Context) (Device, error) { |
| paddy@0 | 109 results, err := GetMany([]uuid.ID{id}, c) |
| paddy@0 | 110 if err != nil { |
| paddy@0 | 111 return Device{}, err |
| paddy@0 | 112 } |
| paddy@0 | 113 result, ok := results[id.String()] |
| paddy@0 | 114 if !ok { |
| paddy@0 | 115 return Device{}, ErrDeviceNotFound |
| paddy@0 | 116 } |
| paddy@0 | 117 return result, nil |
| paddy@0 | 118 } |
| paddy@0 | 119 |
| paddy@0 | 120 // Update applies the DeviceChange to the passed Device, and returns the result. If |
| paddy@0 | 121 // the Device can't be found, an ErrDeviceNotFound error was returned. |
| paddy@0 | 122 func Update(device Device, change DeviceChange, c context.Context) (Device, error) { |
| paddy@0 | 123 storer, err := getStorer(c) |
| paddy@0 | 124 if err != nil { |
| paddy@0 | 125 log.Printf("Error retrieving Storer: %+v\n", err) |
| paddy@0 | 126 return Device{}, err |
| paddy@0 | 127 } |
| paddy@0 | 128 change.DeviceID = device.ID |
| paddy@0 | 129 err = storer.UpdateDevice(change, c) |
| paddy@0 | 130 if err != nil { |
| paddy@0 | 131 return Device{}, err |
| paddy@0 | 132 } |
| paddy@0 | 133 return ApplyChange(device, change), nil |
| paddy@0 | 134 } |
| paddy@0 | 135 |
| paddy@0 | 136 // DeleteMany removes the passed IDs from the datastore. No error is returned if the |
| paddy@0 | 137 // ID doesn't correspond to a Device in the datastore. |
| paddy@0 | 138 func DeleteMany(ids []uuid.ID, c context.Context) error { |
| paddy@0 | 139 storer, err := getStorer(c) |
| paddy@0 | 140 if err != nil { |
| paddy@0 | 141 log.Printf("Error retrieving Storer: %+v\n", err) |
| paddy@0 | 142 return err |
| paddy@0 | 143 } |
| paddy@0 | 144 return storer.DeleteDevices(ids, c) |
| paddy@0 | 145 } |
| paddy@0 | 146 |
| paddy@0 | 147 // Delete removes the passed ID from the datastore. No error is returned if the ID doesn't |
| paddy@0 | 148 // correspond to a Device in the datastore. |
| paddy@0 | 149 func Delete(id uuid.ID, c context.Context) error { |
| paddy@0 | 150 return DeleteMany([]uuid.ID{id}, c) |
| paddy@0 | 151 } |
| paddy@0 | 152 |
| paddy@0 | 153 // CreateMany stores the passed Devices in the datastore, assigning default values if |
| paddy@0 | 154 // necessary. The Devices that were ultimately stored (including any default values, if |
| paddy@0 | 155 // applicable) are returned. |
| paddy@0 | 156 func CreateMany(devices []Device, c context.Context) ([]Device, error) { |
| paddy@0 | 157 storer, err := getStorer(c) |
| paddy@0 | 158 if err != nil { |
| paddy@0 | 159 log.Printf("Error retrieving Storer: %+v\n", err) |
| paddy@0 | 160 return []Device{}, err |
| paddy@0 | 161 } |
| paddy@0 | 162 modified := make([]Device, 0, len(devices)) |
| paddy@0 | 163 for _, device := range devices { |
| paddy@0 | 164 if device.ID.IsZero() { |
| paddy@0 | 165 device.ID = uuid.NewID() |
| paddy@0 | 166 } |
| paddy@0 | 167 if device.Created.IsZero() { |
| paddy@0 | 168 device.Created = time.Now() |
| paddy@0 | 169 } |
| paddy@0 | 170 if device.LastSeen.IsZero() { |
| paddy@0 | 171 device.LastSeen = time.Now() |
| paddy@0 | 172 } |
| paddy@0 | 173 modified = append(modified, device) |
| paddy@0 | 174 } |
| paddy@0 | 175 err = storer.CreateDevices(devices, c) |
| paddy@0 | 176 if err != nil { |
| paddy@0 | 177 return []Device{}, err |
| paddy@0 | 178 } |
| paddy@0 | 179 return modified, nil |
| paddy@0 | 180 } |
| paddy@0 | 181 |
| paddy@0 | 182 // Create stores the passed Device in the datastore, assigning default values if |
| paddy@0 | 183 // necessary. The Devices that were ultimately stored (including any default values, if |
| paddy@0 | 184 // applicable) are returned. |
| paddy@0 | 185 func Create(device Device, c context.Context) (Device, error) { |
| paddy@0 | 186 devices, err := CreateMany([]Device{device}, c) |
| paddy@0 | 187 if err != nil { |
| paddy@0 | 188 return Device{}, err |
| paddy@0 | 189 } |
| paddy@0 | 190 // There should never be a case where we don't return a result. |
| paddy@0 | 191 // Ideally, we'd return an error here instead of letting the panic |
| paddy@0 | 192 // happen, but seeing as I can't come up with a reason the error would |
| paddy@0 | 193 // occur, I'm having trouble coming up with a reasonable error to return. |
| paddy@0 | 194 return devices[0], nil |
| paddy@0 | 195 } |
| paddy@0 | 196 |
| paddy@0 | 197 // ListByOwner returns a slice of all the Devices with an Owner property that |
| paddy@0 | 198 // matches the passed ID. There's no guarantee on the order the Devices will be |
| paddy@0 | 199 // returned in. |
| paddy@0 | 200 func ListByOwner(user uuid.ID, c context.Context) ([]Device, error) { |
| paddy@0 | 201 // BUG(paddy): Eventually, we'll need to support paging for devices. But right now, I don't foresee any user creating enough of them to make pagination worthwhile. |
| paddy@0 | 202 storer, err := getStorer(c) |
| paddy@0 | 203 if err != nil { |
| paddy@0 | 204 log.Printf("Error retrieving Storer: %+v\n", err) |
| paddy@0 | 205 return []Device{}, err |
| paddy@0 | 206 } |
| paddy@0 | 207 devices, err := storer.ListDevicesByOwner(user, c) |
| paddy@0 | 208 return devices, err |
| paddy@0 | 209 } |