Start working on API refactor

This commit is contained in:
Calvin Montgomery 2013-08-07 23:44:41 -04:00
parent 9c83a4dd3e
commit d266175d5b
3 changed files with 126 additions and 180 deletions

271
api.js
View File

@ -28,183 +28,144 @@ module.exports = function (Server) {
return raw; return raw;
} }
var API = function () { function getChannelData(channel) {
var data = {
name: channel.name,
loaded: true
};
} data.pagetitle = channel.opts.pagetitle;
API.prototype = { data.media = channel.playlist.current ?
handle: function (path, req, res) { channel.playlist.current.media.pack() :
var parts = path.split("/"); {};
var last = parts[parts.length - 1]; data.usercount = channel.users.length;
var params = {}; data.afkcount = channel.afkers.length;
if(last.indexOf("?") != -1) { data.users = [];
parts[parts.length - 1] = last.substring(0, last.indexOf("?")); for(var i in channel.users)
var plist = last.substring(last.indexOf("?") + 1).split("&"); if(channel.users[i].name !== "")
for(var i = 0; i < plist.length; i++) { data.users.push(channel.users[i].name);
var kv = plist[i].split("=");
if(kv.length != 2) { data.chat = [];
res.send(400); for(var i in channel.chatbuffer)
return; data.chat.push(channel.chatbuffer[i]);
}
params[unescape(kv[0])] = unescape(kv[1]); return data;
}
}
for(var i = 0; i < parts.length; i++) {
parts[i] = unescape(parts[i]);
} }
if(parts.length != 2) { var app = Server.app;
res.send(400);
return;
}
if(parts[0] == "json") { /* REGION channels */
res.callback = params.callback || false;
if(!(parts[1] in this.jsonHandlers)) { /* data about a specific channel */
res.end(JSON.stringify({ app.get("/api/channels/:channel", function (req, res) {
error: "Unknown endpoint: " + parts[1] var name = req.params.channel;
}, null, 4)); if(!name.match(/^[\w-_]+$/)) {
return;
}
this.jsonHandlers[parts[1]](params, req, res);
}
else if(parts[0] == "plain") {
if(!(parts[1] in this.plainHandlers)) {
res.send(404); res.send(404);
return; return;
} }
this.plainHandlers[parts[1]](params, req, res);
}
else {
res.send(400);
}
},
sendJSON: function (res, obj) { var data = {
var response = JSON.stringify(obj, null, 4); name: name,
if(res.callback) { loaded: false
response = res.callback + "(" + response + ")";
}
var len = unescape(encodeURIComponent(response)).length;
res.setHeader("Content-Type", "application/json");
res.setHeader("Content-Length", len);
res.end(response);
},
sendPlain: function (res, str) {
if(res.callback) {
str = res.callback + "('" + str + "')";
}
var len = unescape(encodeURIComponent(str)).length;
res.setHeader("Content-Type", "text/plain");
res.setHeader("Content-Length", len);
res.end(response);
},
handleChannelData: function (params, req, res) {
var clist = params.channel || "";
clist = clist.split(",");
var data = [];
for(var j = 0; j < clist.length; j++) {
var cname = clist[j];
if(!cname.match(/^[a-zA-Z0-9-_]+$/)) {
continue;
}
var d = {
name: cname,
loaded: Server.channelLoaded(cname)
}; };
if(d.loaded) { if(Server.channelLoaded(name))
var chan = Server.getChannel(cname); data = getChannelData(name);
d.pagetitle = chan.opts.pagetitle;
d.media = chan.playlist.current ? chan.playlist.current.media.pack() : {}; res.type("application/json");
d.usercount = chan.users.length; res.jsonp(data);
d.users = []; });
for(var i = 0; i < chan.users.length; i++) {
if(chan.users[i].name) { /* data about all channels (filter= public or all) */
d.users.push(chan.users[i].name); app.get("/api/allchannels/:filter", function (req, res) {
} var filter = req.params.filter;
} if(filter !== "public" && filter !== "all") {
d.chat = []; res.send(400);
for(var i = 0; i < chan.chatbuffer.length; i++) { return;
d.chat.push(chan.chatbuffer[i]);
}
}
data.push(d);
} }
this.sendJSON(res, data); var query = req.query;
},
handleChannelList: function (params, req, res) { // Listing non-public channels requires authenticating as an admin
if(params.filter == "public") { if(filter !== "public") {
var all = Server.channels; var name = query.name || "";
var clist = []; var session = query.session || "";
for(var key in all) { var row = Auth.login(name, "", session);
if(all[key].opts.show_public) {
clist.push(all[key].name);
}
}
this.handleChannelData({channel: clist.join(",")}, req, res);
}
var session = params.session || "";
var name = params.name || "";
var pw = params.pw || "";
var row = Auth.login(name, pw, session);
if(!row || row.global_rank < 255) { if(!row || row.global_rank < 255) {
res.send(403); res.send(403);
return; return;
} }
var clist = []; }
var channels = [];
for(var key in Server.channels) { for(var key in Server.channels) {
clist.push(Server.channels[key].name); var channel = Server.channels[key];
if(channel.opts.show_public) {
channels.push(getChannelData(channel));
} else if(filter !== "public") {
channels.push(getChannelData(channel));
}
} }
this.handleChannelData({channel: clist.join(",")}, req, res);
},
handleLogin: function (params, req, res) { res.type("application/jsonp");
var session = params.session || ""; res.jsonp(channels);
var name = params.name || "";
var pw = params.pw || "";
if(pw == "" && session == "") {
if(!Auth.isRegistered(name)) {
this.sendJSON(res, {
success: true,
session: ""
}); });
return;
} /* ENDREGION channels */
else {
this.sendJSON(res, { /* REGION authentication */
/* login */
app.post("/api/login", function (req, res) {
res.type("application/jsonp");
var name = req.body.name;
var pw = req.body.pw;
var session = req.body.session;
// for some reason CyTube previously allowed guest logins
// over the API...wat
if(!pw && !session) {
res.jsonp({
success: false, success: false,
error: "That username is already taken" error_code: "need_pw_or_session",
error: "You must provide a password"
}); });
return; return;
} }
}
var row = Auth.login(name, pw, session); var row = Auth.login(name, pw, session);
if(row) { if(!row) {
if(session && !pw) {
res.jsonp({
success: false,
error_code: "invalid_session",
error: "Session expired"
});
return;
} else {
ActionLog.record(getIP(req), name, "login-failure",
"invalid_password");
res.jsonp({
success: false,
error_code: "invalid_password",
error: "Provided username/password pair is invalid"
});
return;
}
}
// record the login if the user is an administrator
if(row.global_rank >= 255) if(row.global_rank >= 255)
ActionLog.record(getIP(req), name, "login-success"); ActionLog.record(getIP(req), name, "login-success");
this.sendJSON(res, {
res.jsonp({
success: true, success: true,
name: name,
session: row.session_hash session: row.session_hash
}); });
}
else {
ActionLog.record(getIP(req), name, "login-failure");
this.sendJSON(res, {
error: "Invalid username/password",
success: false
}); });
}
},
var x = {
handlePasswordChange: function (params, req, res) { handlePasswordChange: function (params, req, res) {
var name = params.name || ""; var name = params.name || "";
var oldpw = params.oldpw || ""; var oldpw = params.oldpw || "";
@ -593,27 +554,5 @@ module.exports = function (Server) {
} }
}; };
var api = new API(); return null;
api.plainHandlers = {
"readlog" : api.handleReadLog.bind(api)
};
api.jsonHandlers = {
"channeldata" : api.handleChannelData.bind(api),
"listloaded" : api.handleChannelList.bind(api),
"login" : api.handleLogin.bind(api),
"register" : api.handleRegister.bind(api),
"changepass" : api.handlePasswordChange.bind(api),
"resetpass" : api.handlePasswordReset.bind(api),
"recoverpw" : api.handlePasswordRecover.bind(api),
"setprofile" : api.handleProfileChange.bind(api),
"getprofile" : api.handleProfileGet.bind(api),
"listuserchannels": api.handleListUserChannels.bind(api),
"setemail" : api.handleEmailChange.bind(api),
"admreports" : api.handleAdmReports.bind(api),
"readactionlog" : api.handleReadActionLog.bind(api)
};
return api;
} }

View File

@ -42,9 +42,11 @@ var defaults = {
} }
function save(cfg, file) { function save(cfg, file) {
if(!cfg.loaded)
return;
var x = {}; var x = {};
for(var k in cfg) { for(var k in cfg) {
if(k !== "nodemailer") if(k !== "nodemailer" && k !== "loaded")
x[k] = cfg[k]; x[k] = cfg[k];
} }
fs.writeFile(file, JSON.stringify(x, null, 4), function (err) { fs.writeFile(file, JSON.stringify(x, null, 4), function (err) {
@ -92,6 +94,8 @@ exports.load = function (Server, file, callback) {
); );
} }
cfg["loaded"] = true;
save(cfg, file); save(cfg, file);
Server.cfg = cfg; Server.cfg = cfg;
callback(); callback();

View File

@ -90,6 +90,7 @@ var Server = {
init: function () { init: function () {
this.httpaccess = new Logger.Logger("httpaccess.log"); this.httpaccess = new Logger.Logger("httpaccess.log");
this.app = express(); this.app = express();
this.app.use(express.bodyParser());
// channel path // channel path
this.app.get("/r/:channel(*)", function (req, res, next) { this.app.get("/r/:channel(*)", function (req, res, next) {
var c = req.params.channel; var c = req.params.channel;
@ -104,10 +105,12 @@ var Server = {
// api path // api path
this.api = require("./api")(this); this.api = require("./api")(this);
/*
this.app.get("/api/:apireq(*)", function (req, res, next) { this.app.get("/api/:apireq(*)", function (req, res, next) {
this.logHTTP(req); this.logHTTP(req);
this.api.handle(req.url.substring(5), req, res); this.api.handle(req.url.substring(5), req, res);
}.bind(this)); }.bind(this));
*/
this.app.get("/", function (req, res, next) { this.app.get("/", function (req, res, next) {
this.logHTTP(req); this.logHTTP(req);