Browse Source

Merge branch 'find-create' of CoBox/cobox-group-store into development

kyphae 2 years ago
parent
commit
faf087ac49
7 changed files with 380 additions and 114 deletions
  1. 4
    2
      README.md
  2. 88
    71
      index.js
  3. 2
    2
      package.json
  4. 252
    32
      test/index.test.js
  5. 21
    0
      test/mocks.js
  6. 7
    1
      util.js
  7. 6
    6
      yarn.lock

+ 4
- 2
README.md View File

@@ -1,6 +1,6 @@
1 1
 # cobox-group-store
2 2
 
3
-A store for loading into memory multiple `cobox-group``'s for iterating over and operating on...
3
+A repository for your cobox-groups. Adhere's to a standard CRUD repository pattern.
4 4
 
5 5
 ## API
6 6
 
@@ -11,5 +11,7 @@ const store = Store(storage)
11 11
 
12 12
 let groups = await store.all() // ensures groups loaded, then returns an array of cobox-groups
13 13
 
14
-let group = await store.get(id) // provide a name string, a 32 byte string, or a 64 byte string. ensures groups loaded, then retrieves group from store.
14
+await store.findBy({ name, address })
15
+await store.where({ name, address })
16
+await store.create({ name, address })
15 17
 ```

+ 88
- 71
index.js View File

@@ -2,11 +2,11 @@ const Config = require('cobox-config')
2 2
 const crypto = require('cobox-crypto')
3 3
 const path = require('path')
4 4
 const thunky = require('thunky')
5
-const debug = require('debug')('cobox-server')
5
+const debug = require('debug')('cobox-group-store')
6 6
 const constants = require('cobox-constants')
7 7
 const maybe = require('call-me-maybe')
8 8
 
9
-const { isString } = require('./util')
9
+const { uniq, isString } = require('./util')
10 10
 
11 11
 module.exports = (createGroup, storage, opts) => new GroupStore(createGroup, storage, opts)
12 12
 
@@ -19,18 +19,10 @@ class GroupStore {
19 19
     this.opts = opts
20 20
     this.createGroup = createGroup
21 21
 
22
-    this.groups = this.config.groups.list().reduce((collection, groupOpts) => {
23
-      var { address, encryptionKey } = groupOpts
24
-
25
-      var group = encryptionKey
26
-        ? this.createGroup(this.storage, address, Object.assign(this.opts, { encryptionKey }))
27
-        : this.createGroup(this.storage, address, this.opts)
28
-
29
-      collection[address] = group
30
-      collection[group.address] = group
31
-      if (group.name) collection[group.name] = group
32
-      return collection
33
-    }, {})
22
+    this.groups = {}
23
+    this.config.groups.list().forEach((groupOpts, i) => {
24
+      this._cacheGroup(this._buildGroup(groupOpts))
25
+    })
34 26
 
35 27
     this._readyCallback = thunky(this._ready.bind(this))
36 28
   }
@@ -44,66 +36,61 @@ class GroupStore {
44 36
     }))
45 37
   }
46 38
 
47
-  all () {
48
-    var self = this
49
-    return new Promise((resolve, reject) => {
50
-      self.ready((err) => {
51
-        if (err) return reject(err)
52
-        resolve(Array.from(new Set(Object.values(self.groups))))
53
-      })
54
-    })
39
+  async all () {
40
+    await this.ready()
41
+    return uniq(Object.values(this.groups))
55 42
   }
56 43
 
57
-  get (id) {
58
-    var self = this
59
-    return new Promise((resolve, reject) => {
60
-      self.ready((err) => {
61
-        if (err) return reject(err)
62
-        var key, name, keys
63
-
64
-        if (crypto.isKey(id)) key = id
65
-        if (!crypto.isKey(id) && isString(id)) name = id
66
-
67
-        // Ensure we either have no key, or the key is a valid key
68
-        if (!(!key || crypto.isKey(key))) return reject(new Error('invalid: key format'))
69
-
70
-        if (name) {
71
-          var group = this.groups[name]
72
-          if (group) return resolve(group)
73
-        }
74
-
75
-        if (key) {
76
-          keys = crypto.unpack(key)
77
-          var group = this.groups[keys.address.toString('hex')]
78
-          if (group) return resolve(group)
79
-        } else {
80
-          key = crypto.accessKey()
81
-          keys = crypto.unpack(key)
82
-        }
83
-
84
-        var { address, encryptionKey } = keys
85
-        var groupOpts = this.config.groups[address]
86
-        if (groupOpts && name && groupOpts.name !== name) return reject(new Error('invalid: group with this name already exists'))
87
-
88
-        var group = encryptionKey
89
-          ? this.createGroup(this.storage, address, Object.assign(this.opts, { encryptionKey }))
90
-          : this.createGroup(this.storage, address, this.opts)
91
-
92
-        this.groups[address.toString('hex')] = group
93
-        this.groups[address] = group
94
-        if (name) this.groups[name] = group
95
-
96
-        if (!groupOpts) {
97
-          if (name) keys.name = name
98
-          this.config.groups.set(address, keys)
99
-          this.config.save()
100
-        }
101
-
102
-        group.ready(() => {
103
-          return resolve(group)
104
-        })
44
+  async where (params = {}) {
45
+    var response = await this.all()
46
+    var keys = Object.keys(params)
47
+
48
+    for (var i = 0; i < keys.length; ++i) {
49
+      var key = keys[i]
50
+
51
+      response = response.filter((entry) => {
52
+        if (!entry[key] || !params[key]) return false
53
+        return entry[key].toString('hex') === params[key].toString('hex')
105 54
       })
106
-    })
55
+    }
56
+
57
+    return response
58
+  }
59
+
60
+  async findBy (params = {}) {
61
+    let response = await this.where(params)
62
+    return response[0]
63
+  }
64
+
65
+  async create (params = {}) {
66
+    var group = await this.findBy(params)
67
+    if (group) throw new Error('group already exists')
68
+
69
+    var group = this._buildGroup(params)
70
+
71
+    await group.ready()
72
+
73
+    this._cacheGroup(group)
74
+    this._saveGroup(group)
75
+
76
+    return group
77
+  }
78
+
79
+  // TODO: this doesn't work currently, throws a level db error
80
+  async destroy (params = {}) {
81
+    var group = await this.findBy(params)
82
+    if (!group) throw new Error('no group exists')
83
+
84
+    this.config.groups.delete(group.address)
85
+    // TODO: delete group data
86
+
87
+    if (this.config.save()) {
88
+      delete this.groups[group.address.toString('hex')]
89
+      return true
90
+    } else {
91
+      this.config.groups.set(group.address, group)
92
+      throw new Error('failed to save config file')
93
+    }
107 94
   }
108 95
 
109 96
   // ------------------------------------------------------------------
@@ -133,4 +120,34 @@ class GroupStore {
133 120
 
134 121
     next(0)
135 122
   }
123
+
124
+  _buildGroup (params = {}) {
125
+    return this.createGroup(
126
+      this.storage,
127
+      params.address,
128
+      Object.assign(this.opts, params)
129
+    )
130
+  }
131
+
132
+  _cacheGroup (group) {
133
+    this.groups[group.address.toString('hex')] = group
134
+    if (group.name) this.groups[group.name] = group
135
+    return true
136
+  }
137
+
138
+  _saveGroup (group) {
139
+    this.config.groups.set(group.address, group.toConfig())
140
+    var success = this.config.save()
141
+    if (!success) return false
142
+    return true
143
+  }
136 144
 }
145
+
146
+// _validate (id) {
147
+//   if (crypto.isKey(id)) key = id
148
+//   if (!crypto.isKey(id) && isString(id)) name = id
149
+//   // Ensure we either have no key, or the key is a valid key
150
+//   if (!(!key || crypto.isKey(key))) throw new Error('invalid: key format')
151
+//   return { key, name }
152
+// }
153
+

+ 2
- 2
package.json View File

@@ -13,9 +13,9 @@
13 13
   "license": "AGPL-3.0-or-later",
14 14
   "dependencies": {
15 15
     "call-me-maybe": "^1.0.1",
16
-    "cobox-crypto": "git+https://ledger-git.dyne.org/cobox/cobox-crypto#development",
17
-    "cobox-constants": "^1.0.0",
18 16
     "cobox-config": "git+https://ledger-git.dyne.org/cobox/cobox-config#development",
17
+    "cobox-constants": "^1.0.0",
18
+    "cobox-crypto": "git+https://ledger-git.dyne.org/cobox/cobox-crypto#development",
19 19
     "cobox-group": "git+https://ledger-git.dyne.org/cobox/cobox-group#development",
20 20
     "debug": "^4.1.1",
21 21
     "thunky": "^1.1.0"

+ 252
- 32
test/index.test.js View File

@@ -1,70 +1,290 @@
1 1
 const { describe } = require('tape-plus')
2 2
 const Config = require('cobox-config')
3
-const { Decrypted, Encrypted } = require('cobox-group')
4 3
 const crypto = require('cobox-crypto')
5 4
 const Store = require('../')
6 5
 
7 6
 const { tmp, cleanup } = require('./util')
7
+const { createGroup } = require('./mocks')
8 8
 
9 9
 describe('group store: basic', (context) => {
10
-  context('on load it caches existing groups from the config', function (assert, next) {
10
+  context('ready()', async function (assert, next) {
11 11
     let storage = tmp(),
12 12
       config = Config(storage),
13
-      group = crypto.keySet()
13
+      params = crypto.keySet()
14 14
 
15
-    config.groups.set(group.address, group)
15
+    config.groups.set(params.address, params)
16 16
 
17
-    const store = Store(Decrypted, storage, { config })
17
+    const store = Store(createGroup, storage, { config })
18 18
 
19
-    store.ready((err) => {
19
+    try {
20
+      await store.ready()
21
+      assert.same(Object.values(store.groups).length, 1, 'builds then caches groups from the config')
22
+      assert.ok(store.groups[params.address.toString('hex')], 'accessible using address hex string')
23
+    } catch (err) {
20 24
       assert.error(err, 'no error')
25
+    }
21 26
 
22
-      assert.ok(Object.values(store.groups).length, 'builds then caches groups from the config')
23
-      assert.ok(store.groups[group.address], 'accessible using buffer')
24
-      assert.ok(store.groups[group.address.toString('hex')], 'accessible using hex')
27
+    cleanup(storage, next)
28
+  })
29
+
30
+  context('all()', async function (assert, next) {
31
+    let storage = tmp(),
32
+      config = Config(storage),
33
+      params = crypto.keySet()
34
+
35
+    config.groups.set(params.address, params)
36
+
37
+    const store = Store(createGroup, storage, { config })
38
+
39
+    try {
40
+      await store.ready()
41
+      var response = await store.all(params)
42
+      assert.ok(Array.isArray(response), 'returns an array')
43
+      assert.same(response.length, 1, 'returns the correct number')
44
+    } catch (err) {
45
+      assert.error(err, 'no error')
46
+    }
47
+
48
+    cleanup(storage, next)
49
+  })
25 50
 
26
-      cleanup(storage, next)
27
-    })
51
+  context('where({ name, address })', async function (assert, next) {
52
+    let storage = tmp(),
53
+      config = Config(storage),
54
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
55
+
56
+    seedConfig()
57
+    config.groups.set(params.address, params)
58
+
59
+    const store = Store(createGroup, storage, { config })
60
+
61
+    try {
62
+      await store.ready()
63
+      var response = await store.where({
64
+        name: 'magma',
65
+        address: params.address
66
+      })
67
+
68
+      assert.ok(Array.isArray(response), 'returns an array')
69
+      var group = response[0]
70
+      assert.ok(group, 'finds a group')
71
+      assert.same(group && group.address, params.address, 'finds the correct group')
72
+    } catch (err) {
73
+      assert.error(err, 'no error')
74
+    }
75
+
76
+    cleanup(storage, next)
77
+
78
+    function seedConfig () {
79
+      var group1 = Object.assign(crypto.keySet(), { name: 'cobox' }),
80
+        group2 = Object.assign(crypto.keySet(), { name: 'iuvia' }),
81
+        group3 = Object.assign(crypto.keySet(), { name: 'blockades' })
82
+
83
+      config.groups.set(group1.address, group1)
84
+      config.groups.set(group2.address, group2)
85
+      config.groups.set(group3.address, group3)
86
+    }
28 87
   })
29 88
 
30
-  context('create a single group', async function (assert, next) {
89
+  context('where({ name, address: null })', async function (assert, next) {
31 90
     let storage = tmp(),
32
-      config = Config(storage)
91
+      config = Config(storage),
92
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
33 93
 
34
-    const store = Store(Decrypted, storage, { config })
94
+    config.groups.set(params.address, params)
95
+
96
+    const store = Store(createGroup, storage, { config })
97
+
98
+    try {
99
+      await store.ready()
100
+
101
+      var response = await store.where({
102
+        name: 'magma',
103
+        address: null
104
+      })
105
+
106
+      assert.same(response.length, 0, 'fails to find a group')
107
+    } catch (err) {
108
+      assert.error(err, 'no error')
109
+    }
35 110
 
36
-    var a = await store.get()
37
-    assert.ok(a, 'creates a new group')
38 111
     cleanup(storage, next)
39 112
   })
40 113
 
41
-  context('get a group by key', async function (assert, next) {
114
+  context('where({ name: null, address })', async function (assert, next) {
42 115
     let storage = tmp(),
43
-      config = Config(storage)
116
+      config = Config(storage),
117
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
44 118
 
45
-    const store = Store(Decrypted, storage, { config })
119
+    config.groups.set(params.address, params)
46 120
 
47
-    var a = await store.get()
48
-    assert.ok(a, 'creates a new group')
121
+    const store = Store(createGroup, storage, { config })
122
+
123
+    try {
124
+      await store.ready()
125
+
126
+      var response = await store.where({
127
+        name: null,
128
+        address: params.address
129
+      })
130
+
131
+      assert.same(response.length, 0, 'fails to find a group')
132
+    } catch (err) {
133
+      assert.error(err, 'no error')
134
+    }
49 135
 
50
-    var b = await store.get(a.key)
51
-    assert.same(a.key, b.key, 'getes the same group by key')
52
-    assert.same(a._id, b._id, 'is the same group instance')
53 136
     cleanup(storage, next)
54 137
   })
55 138
 
56
-  context('get a group by name', async function (assert, next) {
139
+  context('where({ name, address: "wrong address" })', async function (assert, next) {
57 140
     let storage = tmp(),
58
-      config = Config(storage)
141
+      config = Config(storage),
142
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
143
+
144
+    config.groups.set(params.address, params)
59 145
 
60
-    const store = Store(Decrypted, storage, { config })
146
+    const store = Store(createGroup, storage, { config })
61 147
 
62
-    var a = await store.get("Silly String")
63
-    assert.ok(a, 'creates a new group')
148
+    try {
149
+      await store.ready()
150
+
151
+      var response = await store.where({
152
+        name: 'magma',
153
+        address: crypto.randomBytes(32).toString('hex')
154
+      })
155
+
156
+      assert.same(response.length, 0, 'fails to find a group')
157
+    } catch (err) {
158
+      assert.error(err, 'no error')
159
+    }
64 160
 
65
-    var b = await store.get("Silly String")
66
-    assert.same(a.key, b.key, 'getes the same group by key')
67
-    assert.same(a._id, b._id, 'is the same group instance')
68 161
     cleanup(storage, next)
69 162
   })
163
+
164
+  context('where({ name: "wrong name", address })', async function (assert, next) {
165
+    let storage = tmp(),
166
+      config = Config(storage),
167
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
168
+
169
+    config.groups.set(params.address, params)
170
+
171
+    const store = Store(createGroup, storage, { config })
172
+
173
+    try {
174
+      await store.ready()
175
+
176
+      var response = await store.where({
177
+        name: 'cobox',
178
+        address: params.address
179
+      })
180
+
181
+      assert.same(response.length, 0, 'fails to find a group')
182
+    } catch (err) {
183
+      assert.error(err, 'no error')
184
+    }
185
+
186
+    cleanup(storage, next)
187
+  })
188
+
189
+  context('where({ name })', async function (assert, next) {
190
+    let storage = tmp(),
191
+      config = Config(storage),
192
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
193
+
194
+    config.groups.set(params.address, params)
195
+
196
+    const store = Store(createGroup, storage, { config })
197
+
198
+    try {
199
+      await store.ready()
200
+
201
+      var response = await store.where({ name: 'magma' })
202
+      assert.ok(Array.isArray(response), 'returns an array')
203
+      var group = response[0]
204
+      assert.ok(group, 'finds a group')
205
+      assert.same(group && group.address, params.address, 'finds the correct group')
206
+    } catch (err) {
207
+      assert.error(err, 'no error')
208
+    }
209
+
210
+    cleanup(storage, next)
211
+  })
212
+
213
+  context('where({ address })', async function (assert, next) {
214
+    let storage = tmp(),
215
+      config = Config(storage),
216
+      params = crypto.keySet()
217
+
218
+    config.groups.set(params.address, params)
219
+
220
+    const store = Store(createGroup, storage, { config })
221
+
222
+    try {
223
+      await store.ready()
224
+
225
+      var response = await store.where({ address: params.address })
226
+      assert.ok(Array.isArray(response), 'returns an array')
227
+      var group = response[0]
228
+      assert.ok(group, 'finds a group')
229
+      assert.same(group && group.address, params.address, 'finds the correct group')
230
+    } catch (err) {
231
+      assert.error(err, 'no error')
232
+    }
233
+
234
+    cleanup(storage, next)
235
+  })
236
+
237
+  context('findBy()', async function (assert, next) {
238
+    let storage = tmp(),
239
+      config = Config(storage),
240
+      params = crypto.keySet()
241
+
242
+    config.groups.set(params.address, params)
243
+
244
+    const store = Store(createGroup, storage, { config })
245
+
246
+    try {
247
+      await store.ready()
248
+
249
+      delete params.encryptionKey
250
+      var response = await store.findBy(params)
251
+      assert.ok(response, 'finds a group')
252
+      assert.same(response && response.address, params.address, 'finds the correct group')
253
+    } catch (err) {
254
+      assert.error(err, 'no error')
255
+    }
256
+    cleanup(storage, next)
257
+  })
258
+
259
+  context('create()', async function (assert, next) {
260
+    let storage = tmp(),
261
+      config = Config(storage),
262
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
263
+
264
+    const store = Store(createGroup, storage, { config })
265
+
266
+    try {
267
+      var group = await store.create(params)
268
+      assert.ok(group && group.address, 'creates a new group')
269
+    } catch (err) {
270
+      assert.error(err, 'no error')
271
+    }
272
+    cleanup(storage, next)
273
+  })
274
+
275
+  // context('destroy()', async function (assert, next) {
276
+  //   let storage = tmp(),
277
+  //     config = Config(storage),
278
+  //     params = crypto.keySet()
279
+
280
+  //   config.groups.set(params.address, params)
281
+  //   const store = Store(createGroup, storage, { config })
282
+
283
+  //   var groups = await store.all()
284
+  //   console.log(groups.map((group) => group.address.toString('hex')))
285
+  //   var success = await store.destroy(params)
286
+
287
+  //   assert.ok(success, 'deleted the group')
288
+  //   cleanup(storage, next)
289
+  // })
70 290
 })

+ 21
- 0
test/mocks.js View File

@@ -0,0 +1,21 @@
1
+const maybe = require('call-me-maybe')
2
+
3
+function createGroup (storage, address, opts = {}) {
4
+  this.storage = storage
5
+  this.address = Buffer.from(address, 'hex')
6
+  this.encryptionKey = Buffer.from(opts.encryptionKey, 'hex')
7
+  this.name = opts.name
8
+}
9
+
10
+createGroup.prototype.ready = (callback) => maybe(callback, new Promise((resolve, reject) => resolve(true)))
11
+createGroup.prototype.toConfig = function () {
12
+  return {
13
+    name: this.name,
14
+    address: this.address,
15
+    encryptionKey: this.encryptionKey
16
+  }
17
+}
18
+
19
+module.exports = {
20
+  createGroup: (storage, address, opts) => new createGroup(storage, address, opts)
21
+}

+ 7
- 1
util.js View File

@@ -6,6 +6,11 @@ function isFunction (variable) {
6 6
   return variable && typeof variable === 'function'
7 7
 }
8 8
 
9
+function uniq (array) {
10
+  if (!Array.isArray(array)) array = [array]
11
+  return Array.from(new Set(array))
12
+}
13
+
9 14
 function removeEmpty (obj) {
10 15
   return Object.keys(obj)
11 16
     .filter(k => obj[k] != null)
@@ -19,6 +24,7 @@ function removeEmpty (obj) {
19 24
 module.exports = {
20 25
   isString,
21 26
   isFunction,
22
-  removeEmpty
27
+  removeEmpty,
28
+  uniq
23 29
 }
24 30
 

+ 6
- 6
yarn.lock View File

@@ -377,8 +377,8 @@ cobox-constants@^1.0.0:
377 377
 
378 378
 "cobox-crypto@git+https://ledger-git.dyne.org/CoBox/cobox-crypto#development":
379 379
   version "1.1.0"
380
-  uid "0ee347eaf84f88deca469185061be825a86f7fe9"
381
-  resolved "git+https://ledger-git.dyne.org/CoBox/cobox-crypto#0ee347eaf84f88deca469185061be825a86f7fe9"
380
+  uid "7190373065de44fc7c4a2d6c20dfbfbe429d06e6"
381
+  resolved "git+https://ledger-git.dyne.org/CoBox/cobox-crypto#7190373065de44fc7c4a2d6c20dfbfbe429d06e6"
382 382
   dependencies:
383 383
     assert "^2.0.0"
384 384
     bip39 "^3.0.2"
@@ -387,7 +387,8 @@ cobox-constants@^1.0.0:
387 387
 
388 388
 "cobox-crypto@git+https://ledger-git.dyne.org/cobox/cobox-crypto#development":
389 389
   version "1.1.0"
390
-  resolved "git+https://ledger-git.dyne.org/cobox/cobox-crypto#0ee347eaf84f88deca469185061be825a86f7fe9"
390
+  uid "7190373065de44fc7c4a2d6c20dfbfbe429d06e6"
391
+  resolved "git+https://ledger-git.dyne.org/cobox/cobox-crypto#7190373065de44fc7c4a2d6c20dfbfbe429d06e6"
391 392
   dependencies:
392 393
     assert "^2.0.0"
393 394
     bip39 "^3.0.2"
@@ -396,8 +397,7 @@ cobox-constants@^1.0.0:
396 397
 
397 398
 "cobox-crypto@git+ssh://git@ledger-git.dyne.org:2240/CoBox/cobox-crypto.git#development":
398 399
   version "1.1.0"
399
-  uid "0ee347eaf84f88deca469185061be825a86f7fe9"
400
-  resolved "git+ssh://git@ledger-git.dyne.org:2240/CoBox/cobox-crypto.git#0ee347eaf84f88deca469185061be825a86f7fe9"
400
+  resolved "git+ssh://git@ledger-git.dyne.org:2240/CoBox/cobox-crypto.git#7190373065de44fc7c4a2d6c20dfbfbe429d06e6"
401 401
   dependencies:
402 402
     assert "^2.0.0"
403 403
     bip39 "^3.0.2"
@@ -406,7 +406,7 @@ cobox-constants@^1.0.0:
406 406
 
407 407
 "cobox-group@git+https://ledger-git.dyne.org/cobox/cobox-group#development":
408 408
   version "2.0.0"
409
-  resolved "git+https://ledger-git.dyne.org/cobox/cobox-group#b583d77cd37c60e41569abe16a62fbb3f98614e6"
409
+  resolved "git+https://ledger-git.dyne.org/cobox/cobox-group#0d348a3620fa8cfbb7287016aa75b57eacf1d415"
410 410
   dependencies:
411 411
     assert "^2.0.0"
412 412
     call-me-maybe "^1.0.1"