Skip to content

Commit

Permalink
feat: add query endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuhvi committed Nov 16, 2023
1 parent d140aef commit 943a6f0
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 7 deletions.
48 changes: 46 additions & 2 deletions lib/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,20 @@ class Relay {
return
}

const url = new URL('http://example.com' + req.url)

switch (req.method) {
case 'OPTIONS':
this._OPTIONS(req, res)
break
case 'GET':
if (req.url.startsWith('/subscribe/')) {
this._SUBSCRIBE(req, res)
return
} else if (url.pathname.endsWith('/')) {
this._QUERY(req, res, url)
} else {
this._GET(req, res)
}
this._GET(req, res)
break
case 'PUT':
this._PUT(req, res)
Expand Down Expand Up @@ -491,6 +495,46 @@ class Relay {
})
}

/**
* @param {string} id
* @param {string} directory
* @param {object} [options]
* @param {number} [options.limit]
* @param {number} [options.offset]
*
* @returns {lmdb.RangeIterable}
*/
_searchDirectory (id, directory, options = {}) {
const range = { start: '/' + id + directory, end: '/' + id + directory.slice(0, directory.length - 1) + '0', ...options }

return this._recordsDB.getRange(range)
}

/**
* @param {http.IncomingMessage} _req
* @param {http.ServerResponse} res
* @param {URL} url
*/
async _QUERY (_req, res, url) {
const [id, ...rest] = url.pathname.slice(1).split('/')
const dir = '/' + rest.join('/')

const options = {
// Skip options until we really need them. currently, it should be cheap to return 1000s of keys at once.

// limit: parseInt(url.searchParams.get('limit')) || 1000,
// offset: parseInt(url.searchParams.get('offset')) || 0
}

const keys = this._searchDirectory(id, dir, options).asArray.map(r => {
this._updateLastQueried(r.key)
return r.key
})

res.writeHead(200)
res.end(keys.join('\n'))
}

/**
* Health check endpoint to provide server metrics.
*
Expand Down
73 changes: 68 additions & 5 deletions test/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ test('missing header', async (t) => {

const response = await fetch(
address + '/' + ZERO_ID + '/test.txt', {
method: 'PUT',
body: 'ffff'
})
method: 'PUT',
body: 'ffff'
})

t.is(response.status, 400)
t.is(response.statusText, `Missing or malformed header: '${HEADERS.RECORD}'`)
Expand Down Expand Up @@ -535,11 +535,74 @@ test('save query dates to help prunning abandoned records later', async (t) => {
relay.close()
})

function tmpdir () {
test('query all in a directory', async (t) => {
const relay = new Relay(tmpdir(), { _writeInterval: 1 })
const address = await relay.listen()

const keyPair = createKeyPair(ZERO_SEED)

const LESSER_ID = '8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewn'
const GREATER_ID = '9pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo'

for (let i = 0; i < 10; i++) {
const path = '/' + ZERO_ID + `/dir/subdir/foo${i}.txt`
const content = Buffer.from('foo content')
const record = await Record.create(keyPair, path, content, { timestamp: 1000 })

const bytes = record.serialize()

await relay._recordsDB.put(path, bytes)

/// False positive subdirectory
await relay._recordsDB.put('/' + ZERO_ID + `/dir/wrong/foo${i}.txt`, bytes)

/// False positive IDs
await relay._recordsDB.put('/' + LESSER_ID + `/dir/subdir/foo${i}.txt`, bytes)
await relay._recordsDB.put('/' + GREATER_ID + `/dir/subdir/foo${i}.txt`, bytes)
}

// let result = relay._recordsDB.getRange({ start: "/" + ZERO_ID + "/", end: "/" + ZERO_ID + "0" }).asArray.map(({ key, value }) => {
// return { key, value: Record.deserialize(value) }
// })
//
// console.log(result)

const headers = {
[HEADERS.CONTENT_TYPE]: 'application/octet-stream'
}

const response = await fetch(address + '/' + ZERO_ID + '/dir/subdir/?something', {
method: 'GET',
headers
})

const keys = await response.text()

const list = keys.split('\n')

t.is(list.length, 10)

t.alike(list, [
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo0.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo1.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo2.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo3.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo4.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo5.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo6.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo7.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo8.txt',
'/8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo/dir/subdir/foo9.txt'
])

relay.close()
})

function tmpdir() {
return path.join(os.tmpdir(), Math.random().toString(16).slice(2))
}

/** @param {number} ms */
function sleep (ms) {
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}

0 comments on commit 943a6f0

Please sign in to comment.