Browse Source

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

kyphae 2 years ago
parent
commit
8b47cb6d63
7 changed files with 471 additions and 1943 deletions
  1. 4
    2
      README.md
  2. 94
    70
      index.js
  3. 2
    3
      package.json
  4. 267
    34
      test/index.test.js
  5. 21
    0
      test/mocks.js
  6. 7
    1
      util.js
  7. 76
    1833
      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
 ```

+ 94
- 70
index.js View File

@@ -1,35 +1,28 @@
1
-const Group = require('cobox-group')
2 1
 const Config = require('cobox-config')
3
-const Crypto = require('cobox-crypto')
2
+const crypto = require('cobox-crypto')
4 3
 const path = require('path')
5 4
 const thunky = require('thunky')
6
-const debug = require('debug')('cobox-server')
5
+const debug = require('debug')('cobox-group-store')
7 6
 const constants = require('cobox-constants')
8 7
 const maybe = require('call-me-maybe')
9 8
 
10
-const crypto = Crypto() 
11
-const { isString } = require('./util')
9
+const { uniq, isString } = require('./util')
12 10
 
13
-module.exports = (storage, opts) => new GroupStore(storage, opts)
11
+module.exports = (createGroup, storage, opts) => new GroupStore(createGroup, storage, opts)
14 12
 
15 13
 class GroupStore {
16
-  constructor (storage, opts = {}) {
14
+  constructor (createGroup, storage, opts = {}) {
17 15
     this._id = crypto.randomBytes(16).toString('hex')
18 16
     this.root = storage || constants.storage
19 17
     this.storage = path.join(this.root, 'groups')
20 18
     this.config = opts.config
21 19
     this.opts = opts
20
+    this.createGroup = createGroup
22 21
 
23
-    this.groups = this.config.groups.list().reduce((collection, groupOpts) => {
24
-      var key
25
-      if (!groupOpts.symmetricKey) key = groupOpts.publicKey
26
-      else key = crypto.pack(groupOpts.publicKey, groupOpts.symmetricKey)
27
-      var group = Group(this.storage, key, this.opts)
28
-      collection[groupOpts.publicKey] = group
29
-      collection[group.publicKey] = group
30
-      if (group.name) collection[group.name] = group
31
-      return collection
32
-    }, {})
22
+    this.groups = {}
23
+    this.config.groups.list().forEach((groupOpts, i) => {
24
+      this._cacheGroup(this._buildGroup(groupOpts))
25
+    })
33 26
 
34 27
     this._readyCallback = thunky(this._ready.bind(this))
35 28
   }
@@ -43,61 +36,61 @@ class GroupStore {
43 36
     }))
44 37
   }
45 38
 
46
-  all () {
47
-    var self = this
48
-    return new Promise((resolve, reject) => {
49
-      self.ready(() => {
50
-        return resolve(Array.from(new Set(Object.values(self.groups))))
51
-      })
52
-    })
39
+  async all () {
40
+    await this.ready()
41
+    return uniq(Object.values(this.groups))
53 42
   }
54 43
 
55
-  get (id) {
56
-    var self = this
57
-    return new Promise((resolve, reject) => {
58
-      self.ready(() => {
59
-        var key, name, keys
60
-
61
-        if (crypto.isKey(id)) key = id
62
-        if (!crypto.isKey(id) && isString(id)) name = id
63
-
64
-        // Ensure we either have no key, or the key is a valid key
65
-        if (!(!key || crypto.isKey(key))) return reject(new Error('invalid: key format'))
66
-
67
-        if (name) {
68
-          var group = this.groups[name]
69
-          if (group) return resolve(group)
70
-        }
71
-
72
-        if (key) {
73
-          keys = crypto.unpack(key)
74
-          var group = this.groups[keys.publicKey.toString('hex')]
75
-          if (group) return resolve(group)
76
-        } else {
77
-          key = crypto.accessKey()
78
-          keys = crypto.unpack(key)
79
-        }
80
-
81
-        var groupOpts = this.config.groups[keys.publicKey]
82
-        if (groupOpts && name && groupOpts.name !== name) return reject(new Error('invalid: group with this name already exists'))
83
-
84
-        var group = Group(this.storage, key, this.opts)
85
-        this.groups[group.publicKey.toString('hex')] = group
86
-        this.groups[group.publicKey] = group
87
-        if (name) this.groups[name] = group
88
-
89
-        if (!groupOpts) {
90
-          groupOpts = group.keys
91
-          if (name) groupOpts.name = name
92
-          this.config.groups.set(group.publicKey, groupOpts)
93
-          this.config.save()
94
-        }
95
-
96
-        group.ready(() => {
97
-          return resolve(group)
98
-        })
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')
99 54
       })
100
-    })
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
+    }
101 94
   }
102 95
 
103 96
   // ------------------------------------------------------------------
@@ -105,9 +98,10 @@ class GroupStore {
105 98
   _ready (callback) {
106 99
     var pending = 1
107 100
     var self = this
101
+    var groups = Object.values(self.groups)
108 102
 
109 103
     function next (n) {
110
-      var group = self.groups[n]
104
+      var group = groups[n]
111 105
       if (!group) return done()
112 106
       ++pending
113 107
       process.nextTick(next, n + 1)
@@ -126,4 +120,34 @@ class GroupStore {
126 120
 
127 121
     next(0)
128 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
+  }
129 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
- 3
package.json View File

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

+ 267
- 34
test/index.test.js View File

@@ -1,57 +1,290 @@
1 1
 const { describe } = require('tape-plus')
2 2
 const Config = require('cobox-config')
3
-const Group = require('cobox-group')
4
-const crypto = require('cobox-crypto')()
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
-  let storage, config
10
+  context('ready()', async function (assert, next) {
11
+    let storage = tmp(),
12
+      config = Config(storage),
13
+      params = crypto.keySet()
11 14
 
12
-  context.beforeEach((c) => {
13
-    storage = tmp()
14
-    config = Config(storage)
15
+    config.groups.set(params.address, params)
16
+
17
+    const store = Store(createGroup, storage, { config })
18
+
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) {
24
+      assert.error(err, 'no error')
25
+    }
26
+
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
+  })
50
+
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
+    }
87
+  })
88
+
89
+  context('where({ name, address: null })', async function (assert, next) {
90
+    let storage = tmp(),
91
+      config = Config(storage),
92
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
93
+
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
+    }
110
+
111
+    cleanup(storage, next)
112
+  })
113
+
114
+  context('where({ name: null, address })', async function (assert, next) {
115
+    let storage = tmp(),
116
+      config = Config(storage),
117
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
118
+
119
+    config.groups.set(params.address, params)
120
+
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
+    }
135
+
136
+    cleanup(storage, next)
137
+  })
138
+
139
+  context('where({ name, address: "wrong address" })', async function (assert, next) {
140
+    let storage = tmp(),
141
+      config = Config(storage),
142
+      params = Object.assign(crypto.keySet(), { name: 'magma' })
143
+
144
+    config.groups.set(params.address, params)
145
+
146
+    const store = Store(createGroup, storage, { config })
147
+
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
+    }
160
+
161
+    cleanup(storage, next)
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)
15 187
   })
16 188
 
17
-  context('on load it caches existing groups from the config', function (assert, next) {
18
-    var group = crypto.keySet()
19
-    config.groups.set(group.publicKey, group)
20
-    const store = Store(storage, { config })
21
-    store.ready(() => {
22
-      assert.ok(Object.values(store.groups).length, 'builds then caches groups from the config')
23
-      assert.ok(store.groups[group.publicKey], 'accessible using buffer')
24
-      assert.ok(store.groups[group.publicKey.toString('hex')], 'accessible using hex')
25
-      cleanup(storage, next)
26
-    })
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)
27 211
   })
28 212
 
29
-  context('create a single group', async function (assert, next) {
30
-    const store = Store(storage, { config })
31
-    var a = await store.get()
32
-    assert.ok(a, 'creates a new group')
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
+
33 234
     cleanup(storage, next)
34 235
   })
35 236
 
36
-  context('get a group by key', async function (assert, next) {
37
-    const store = Store(storage, { config })
38
-    var a = await store.get()
39
-    assert.ok(a, 'creates a new group')
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 })
40 245
 
41
-    var b = await store.get(a.key)
42
-    assert.same(a.key, b.key, 'getes the same group by key')
43
-    assert.same(a._id, b._id, 'is the same group instance')
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
+    }
44 256
     cleanup(storage, next)
45 257
   })
46 258
 
47
-  context('get a group by name', async function (assert, next) {
48
-    const store = Store(storage, { config })
49
-    var a = await store.get("Silly String")
50
-    assert.ok(a, 'creates a new group')
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 })
51 265
 
52
-    var b = await store.get("Silly String")
53
-    assert.same(a.key, b.key, 'getes the same group by key')
54
-    assert.same(a._id, b._id, 'is the same group instance')
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
+    }
55 272
     cleanup(storage, next)
56 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
+  // })
57 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
 

+ 76
- 1833
yarn.lock
File diff suppressed because it is too large
View File