Skip to content

Commit

Permalink
Merge pull request #176 from mbland/search-on-target-link
Browse files Browse the repository at this point in the history
Search on target link
  • Loading branch information
mbland committed Apr 12, 2018
2 parents dfe6230 + bc67d9a commit f6c12b2
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 10 deletions.
4 changes: 4 additions & 0 deletions lib/link-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ LinkDb.prototype.getLinkIfOwner = function(link, user) {
})
}

LinkDb.prototype.searchTargetLinks = function(searchString) {
return this.client.searchTargetLinks(searchString)
}

LinkDb.prototype.updateProperty = function(link, user, property, value) {
return this.getLinkIfOwner(link, user)
.then(() => {
Expand Down
35 changes: 32 additions & 3 deletions lib/redis/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var ListHelper = require('./list-helper')
var SearchHelper = require('./search-helper')
var AutocompleteIndexer = require('./autocomplete-indexer')
var TargetIndexer = require('./target-indexer')
var Keys = require('./keys')

module.exports = RedisClient

Expand Down Expand Up @@ -54,11 +55,38 @@ RedisClient.prototype.getLink = function(link) {
})
}

RedisClient.prototype.getLinks = function(searchString) {
var searchHelper = new SearchHelper(this.impl, searchString)
function getShortLinksFromTargetLinks(redisClient, targetKeys) {
return Promise.all(targetKeys.map(targetKey =>
getShortLinksFromTargetLink(redisClient, targetKey)
)).then(shortLinks =>
targetKeys.reduce((results, targetIndexKey, i) => {
results[Keys.getLinkFromTargetIndexKey(targetIndexKey)] = shortLinks[i]
return results
}, {})
)
}

function getShortLinksFromTargetLink(redisClient, targetLink) {
return new Promise((resolve, reject) => {
redisClient.impl.lrange(targetLink, 0, -1, (err, shortLinks) => {
return err ? reject(err) : resolve(shortLinks)
})
})
}

RedisClient.prototype.searchShortLinks = function(searchString) {
var searchHelper = new SearchHelper(this.impl, searchString,
Keys.SHORT_LINK_PREFIX)
return searchHelper.scan().then(links => this.fetchLinkData(links.sort()))
}

RedisClient.prototype.searchTargetLinks = function(searchString) {
var searchHelper = new SearchHelper(this.impl, searchString,
Keys.TARGET_LINK_INDEX_PREFIX)
return searchHelper.scan().then(links =>
getShortLinksFromTargetLinks(this, links))
}

RedisClient.prototype.recordAccess = function(link) {
return new Promise((resolve, reject) => {
this.impl.hincrby(link, 'clicks', 1, err => err ? reject(err) : resolve())
Expand All @@ -80,7 +108,7 @@ RedisClient.prototype.removeLinkFromOwner = function(owner, link) {
RedisClient.prototype.createLink = function(link, target, owner) {
return new Promise((resolve, reject) => {
var createdStamp = this.getTimestamp()

const linkInfo = { target: target }
this.impl.hsetnx(link, 'owner', owner, (err, result) => {
if (err) {
return reject(err)
Expand All @@ -97,6 +125,7 @@ RedisClient.prototype.createLink = function(link, target, owner) {
return reject(new Error(link + ' created, ' +
'but failed to set target, clicks, and timestamps: ' + err))
}
PromiseHelper.map(this.indexers, i => i.addLink(link, linkInfo))
resolve(true)
})
})
Expand Down
15 changes: 15 additions & 0 deletions lib/redis/keys.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
'use strict'

module.exports = class Keys {

static get SHORT_LINK_PREFIX() {
return SHORT_LINK_PREFIX
}

static get TARGET_LINK_INDEX_PREFIX() {
return TARGET_LINK_INDEX_PREFIX
}

static build() {
return Array.prototype.slice.call(arguments).join(':')
}
Expand All @@ -12,6 +21,12 @@ module.exports = class Keys {
static targetIndex(target) {
return Keys.build('target', target)
}

static getLinkFromTargetIndexKey(targetIndexKey) {
return targetIndexKey.slice(TARGET_LINK_INDEX_PREFIX.length)
}
}

const COMPLETE_LINKS_SET_KEY = module.exports.build('complete', 'links')
const SHORT_LINK_PREFIX = '/'
const TARGET_LINK_INDEX_PREFIX = 'target:'
6 changes: 3 additions & 3 deletions lib/redis/search-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
var PromiseHelper = require('./promise-helper')

module.exports = class SearchHelper {
constructor(redisClient, searchString) {
constructor(redisClient, searchString, keyPrefix) {
this.links = []
this.redisClient = redisClient
this.cursor = 0
this.regExp = '/*' + (searchString ? searchString + '*' : '')
this.pattern = `${keyPrefix}*${searchString ? searchString + '*' : ''}`
}

collectResults([cursorNext, results]) {
Expand All @@ -18,7 +18,7 @@ module.exports = class SearchHelper {

processResults() {
return PromiseHelper.do(done => {
this.redisClient.scan(this.cursor, 'match', this.regExp, done)
this.redisClient.scan(this.cursor, 'match', this.pattern, done)
})
}

Expand Down
15 changes: 15 additions & 0 deletions tests/server/link-db-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,21 @@ describe('LinkDb', function() {
.should.be.rejectedWith('/foo is owned by msb')
})
})

describe('searchTargetLinks', function() {
it('returns all links', () => {
stubClientMethod('searchTargetLinks')
.withArgs('')
.returns(Promise.resolve({
'https://mike-bland.com/': ['/baz', '/bar', '/foo'],
'https://akash.com': ['/test']
}))
return linkDb.searchTargetLinks('').should.become({
'https://mike-bland.com/': ['/baz', '/bar', '/foo'],
'https://akash.com': ['/test']
})
})
})

describe('updateProperty', function() {
it('successfully changes the target', function() {
Expand Down
45 changes: 41 additions & 4 deletions tests/server/redis-client-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,9 +483,9 @@ describe('RedisClient', function() {
})
})

describe('getLinks', function() {
describe('searchShortLinks', function() {
it('should return nothing if there are no links', function() {
return redisClient.getLinks().should.become([])
return redisClient.searchShortLinks().should.become([])
})

it('should return all links', function() {
Expand All @@ -494,7 +494,7 @@ describe('RedisClient', function() {
redisClient.createLink('/bar', LINK_TARGET, 'mbland'),
redisClient.createLink('/baz', LINK_TARGET, 'mbland')
]).should.be.fulfilled.then(function() {
return redisClient.getLinks()
return redisClient.searchShortLinks()
}).should.be.fulfilled.then(function(links) {
links.map(l => l.link).should.eql(['/bar', '/baz', '/foo'])
})
Expand All @@ -507,14 +507,51 @@ describe('RedisClient', function() {
redisClient.createLink('/bar1', LINK_TARGET, 'akash'),
redisClient.createLink('/alphafoo1', LINK_TARGET, 'akash')
]).should.be.fulfilled.then(function() {
return redisClient.getLinks('foo')
return redisClient.searchShortLinks('foo')
}).should.be.fulfilled.then(function(links){
links.map(l => l.link).sort()
.should.eql(['/alphafoo1', '/foo1', '/foo2'])
})
})
})

describe('searchTargetLinks', function() {
it('should return nothing if there are no links', function() {
return redisClient.searchTargetLinks().should.become({})
})

it('should return all links', function() {
return Promise.all([
redisClient.createLink('/foo', LINK_TARGET, 'akash'),
redisClient.createLink('/bar', LINK_TARGET, 'akash'),
redisClient.createLink('/baz', LINK_TARGET, 'akash'),
redisClient.createLink('/test', 'https://akash.com', 'akash')
]).should.be.fulfilled.then(function() {
return redisClient.searchTargetLinks()
}).should.be.fulfilled.then(function(link) {
link.should.eql({
'https://mike-bland.com/': ['/baz', '/bar', '/foo'],
'https://akash.com': ['/test']
})
})
})

it('should return all matching links and their shortlinks', function() {
return Promise.all([
redisClient.createLink('/foo', LINK_TARGET, 'akash'),
redisClient.createLink('/bar', LINK_TARGET, 'akash'),
redisClient.createLink('/baz', LINK_TARGET, 'akash'),
redisClient.createLink('/test', 'https://akash.com', 'akash')
]).should.be.fulfilled.then(function() {
return redisClient.searchTargetLinks('akash')
}).should.be.fulfilled.then(function(link) {
link.should.eql({
'https://akash.com': ['/test']
})
})
})
})

describe('completeLink', function() {
beforeEach(function() {
return Promise.all([
Expand Down

0 comments on commit f6c12b2

Please sign in to comment.