taggable-via-redis
Version:
Add tagging functionality backed by Redis. This is a rewrite of sintaxi's node-redis-tag.
225 lines (209 loc) • 8.48 kB
JavaScript
// Generated by CoffeeScript 1.6.3
(function() {
var EMPTY_ARRAY, EMPTY_STRING, PREFIX, REDIS_CLIENT, assert, debuglog, redis, scopedSet, unscopedSet;
redis = require("redis");
debuglog = require("debug")("Taggable");
assert = require("assert");
EMPTY_STRING = '';
EMPTY_ARRAY = [];
PREFIX = "_T";
REDIS_CLIENT = null;
scopedSet = function(type, scope, id, tags, callback) {
var newList,
_this = this;
debuglog("[scopedSet] type:" + type + ", scope:" + scope + ", id:" + id + ", tags:" + tags);
newList = tags;
REDIS_CLIENT.smembers("" + PREFIX + ":" + scope + ":" + type + ":" + id + ":tags", function(err, oldList) {
var added, removed, toAddCount, toRemoveCount;
if (err != null) {
return typeof callback === "function" ? callback(err) : void 0;
}
oldList = oldList || [];
added = newList.filter(function(i) {
return oldList.indexOf(i) === -1;
});
removed = oldList.filter(function(i) {
return newList.indexOf(i) === -1;
});
toAddCount = added.length;
toRemoveCount = removed.length;
if (toAddCount === 0 && toRemoveCount === 0) {
return typeof callback === "function" ? callback() : void 0;
}
added.forEach(function(tag) {
REDIS_CLIENT.multi().sadd("" + PREFIX + ":" + scope + ":" + type + ":" + id + ":tags", tag).sadd("" + PREFIX + ":" + scope + ":" + type + ":tags:" + tag, id).zincrby("" + PREFIX + ":" + scope + ":" + type + ":tags", 1, tag).sadd("" + PREFIX + ":" + type + ":" + id + ":tags", tag).sadd("" + PREFIX + ":" + type + ":tags:" + tag, id).zincrby("" + PREFIX + ":" + type + ":tags", 1, tag).exec(function(err, replies) {
if (err != null) {
return typeof callback === "function" ? callback(err) : void 0;
}
toAddCount--;
if (toAddCount <= 0 && toRemoveCount <= 0) {
return typeof callback === "function" ? callback() : void 0;
}
});
});
removed.forEach(function(tag) {
REDIS_CLIENT.multi().srem("" + PREFIX + ":" + scope + ":" + type + ":" + id + ":tags", tag).srem("" + PREFIX + ":" + scope + ":" + type + ":tags:" + tag, id).zincrby("" + PREFIX + ":" + scope + ":" + type + ":tags", -1, tag).srem("" + PREFIX + ":" + type + ":" + id + ":tags", tag).srem("" + PREFIX + ":" + type + ":tags:" + tag, id).zincrby("" + PREFIX + ":" + type + ":tags", -1, tag).exec(function(err, replies) {
if (err != null) {
return typeof callback === "function" ? callback(err) : void 0;
}
if (replies[2] === "0") {
REDIS_CLIENT.zrem("" + PREFIX + ":" + scope + ":" + type + ":tags", tag);
}
if (replies[5] === "0") {
REDIS_CLIENT.zrem("" + PREFIX + ":" + type + ":tags", tag);
}
toRemoveCount--;
if (toAddCount <= 0 && toRemoveCount <= 0) {
if (typeof callback === "function") {
callback();
}
}
});
});
});
};
unscopedSet = function(type, id, tags, callback) {
var newList,
_this = this;
debuglog("[unscopedSet] type:" + type + ", id:" + id + ", tags:" + tags);
newList = tags;
REDIS_CLIENT.smembers("" + PREFIX + ":" + type + ":" + id + ":tags", function(err, oldList) {
var added, removed, toAddCount, toRemoveCount;
oldList = oldList || [];
removed = oldList.filter(function(i) {
return newList.indexOf(i) === -1;
});
added = newList.filter(function(i) {
return oldList.indexOf(i) === -1;
});
toAddCount = added.length;
toRemoveCount = removed.length;
if (toAddCount === 0 && toRemoveCount === 0) {
return typeof callback === "function" ? callback() : void 0;
}
added.forEach(function(tag) {
REDIS_CLIENT.multi().sadd("" + PREFIX + ":" + type + ":" + id + ":tags", tag).sadd("" + PREFIX + ":" + type + ":tags:" + tag, id).zincrby("" + PREFIX + ":" + type + ":tags", 1, tag).exec(function(err, replies) {
if (err != null) {
return typeof callback === "function" ? callback(err) : void 0;
}
toAddCount--;
if (toAddCount === 0 && toRemoveCount === 0) {
if (typeof callback === "function") {
callback();
}
}
});
});
removed.forEach(function(tag) {
REDIS_CLIENT.multi().srem("" + PREFIX + ":" + type + ":" + id + ":tags", tag).srem("" + PREFIX + ":" + type + ":tags:" + tag, id).zincrby("" + PREFIX + ":" + type + ":tags", -1, tag).exec(function(err, replies) {
if (replies[2] === "0") {
REDIS_CLIENT.zrem("" + PREFIX + ":" + type + ":tags", tag);
}
toRemoveCount--;
if (toAddCount === 0 && toRemoveCount === 0) {
if (typeof callback === "function") {
callback();
}
}
});
});
});
};
exports.init = function(redisClient, prefix) {
REDIS_CLIENT = redisClient || REDIS_CLIENT;
PREFIX = prefix || PREFIX;
};
exports.set = function(type, id, tags, scope, callback) {
assert(REDIS_CLIENT, "redis client not init");
type = String(type || EMPTY_STRING);
assert(type, "bad argument type:" + type + ")");
id = String(id || EMPTY_STRING);
assert(id, "bad argument id:" + id + ")");
if ('function' === typeof scope) {
callback = scope;
scope = null;
}
tags = tags || EMPTY_ARRAY;
debuglog("[set] type:" + type + ", id:" + id + ", tags:" + tags + ", scope:" + scope + ",");
if (scope) {
scopedSet(type, scope, id, tags, callback);
} else {
unscopedSet(type, id, tags, callback);
}
};
exports.get = function(type, ids, scope, callback) {
var id, proc, _i, _len;
if ('function' === typeof scope) {
callback = scope;
scope = "";
} else {
scope = scope != null ? "" + scope + ":" : EMPTY_STRING;
}
debuglog("[get] type:" + type + ", ids:" + ids + ", scope:" + scope);
if (!ids) {
return typeof callback === "function" ? callback(null, []) : void 0;
}
if (!(Array.isArray(ids) && ids.length > 0)) {
REDIS_CLIENT.smembers("" + PREFIX + ":" + scope + type + ":" + ids + ":tags", callback);
} else {
proc = REDIS_CLIENT.multi();
for (_i = 0, _len = ids.length; _i < _len; _i++) {
id = ids[_i];
proc = proc.smembers("" + PREFIX + ":" + scope + type + ":" + id + ":tags");
}
proc.exec(callback);
}
};
exports.find = function(type, tags, scope, callback) {
var i, sets, tag, _i, _len;
assert(REDIS_CLIENT, "redis client not init");
if ('function' === typeof scope) {
callback = scope;
scope = EMPTY_STRING;
} else {
scope = scope != null ? "" + scope + ":" : EMPTY_STRING;
}
debuglog("[find] type:" + type + ", tags:" + tags + ", scope:" + scope);
if (!(tags || EMPTY_STRING).toString()) {
return callback(null, []);
}
sets = [];
if (Array.isArray(tags)) {
for (i = _i = 0, _len = tags.length; _i < _len; i = ++_i) {
tag = tags[i];
sets.push("" + PREFIX + ":" + scope + type + ":tags:" + tag);
}
} else {
sets.push("" + PREFIX + ":" + scope + type + ":tags:" + tags);
}
REDIS_CLIENT.sinter(sets, callback);
};
exports.popular = function(type, count, scope, callback) {
var key;
assert(REDIS_CLIENT, "redis client not init");
count = parseInt(count, 10);
assert(count > 0, "bad argument count:" + count);
if ('function' === typeof scope) {
callback = scope;
scope = EMPTY_STRING;
} else {
scope = scope != null ? "" + scope + ":" : EMPTY_STRING;
}
debuglog("[popular] type:" + type + ", count:" + count + ", scope:" + scope);
key = "" + PREFIX + ":" + scope + type + ":tags";
REDIS_CLIENT.zrevrange(key, 0, count - 1, "WITHSCORES", function(err, reply) {
var i, item, results, _i, _len;
if (err != null) {
return typeof callback === "function" ? callback(err) : void 0;
}
results = [];
for (i = _i = 0, _len = reply.length; _i < _len; i = _i += 2) {
item = reply[i];
results.push([reply[i], parseInt(reply[i + 1], 10)]);
}
if (typeof callback === "function") {
callback(null, results);
}
});
};
}).call(this);