move everything to Meteor methods

This commit is contained in:
Andrew Zhu
2019-02-07 22:05:24 -08:00
parent 2f04d9ec1c
commit cb71f6d380
3 changed files with 353 additions and 251 deletions

View File

@@ -69,266 +69,144 @@ Router.map(function () {
this.route("addSpellsToCharacter", { // POST /api/character/:_id/spellList/:listId this.route("addSpellsToCharacter", { // POST /api/character/:_id/spellList/:listId
path: "/api/character/:_id/spellList/:listId", path: "/api/character/:_id/spellList/:listId",
where: "server" where: "server"
}).post( }).post(function () {
function () { const key = startPOSTResponse(this);
ifPostOK(this, "addSpellsToList", () => { const spells = this.request.body;
const spells = this.request.body; const charId = this.params._id;
const charId = this.params._id; const listId = this.params.listId;
const listId = this.params.listId; Meteor.call("insertSpells", key, charId, listId, spells, (err, res) => {
let spellIds = []; if (err) {
let error; console.log(err);
for (let spell of spells) { this.response.writeHead(err.error, err.reason);
spell.parent = {id: listId, collection: "SpellLists"}; this.response.end(err.details);
spell.charId = charId; } else {
let id = Spells.insert(spell, (err, _id) => { this.response.end(JSON.stringify(res));
if (err) { }
error = err.message; });
} });
});
if (error)
break;
spellIds.push(id);
}
if (error) {
this.response.writeHead(400, "Failed to insert one or more spells");
this.response.end(JSON.stringify({err: error, inserted: spellIds}));
} else {
this.response.end(JSON.stringify(spellIds));
}
});
}
);
this.route("createCharacter", { // POST /api/character this.route("createCharacter", { // POST /api/character
path: "/api/character", path: "/api/character",
where: "server" where: "server"
}).post( }).post(function () {
function () { const key = startPOSTResponse(this);
this.response.setHeader("Content-Type", "application/json"); const character = this.request.body;
const header = this.request.headers; Meteor.call("insertCharacter", key, character, (err, res) => {
const key = header && header['authorization']; if (err) {
ifKeyValid(key, this.response, "createCharacter", () => { this.response.writeHead(err.error, err.reason);
const character = this.request.body; this.response.end(err.details);
let error; } else {
this.response.end(JSON.stringify(res));
character.owner = userIdFromKey(key); }
let id = Characters.insert(character, (err) => { });
if (err) });
error = err.message;
});
if (error) {
this.response.writeHead(400, "Failed to insert character");
this.response.end(JSON.stringify({err: error}));
} else {
this.response.end(JSON.stringify({id: id}));
}
});
}
);
this.route("deleteCharacter", { // DELETE /api/character/:_id this.route("deleteCharacter", { // DELETE /api/character/:_id
path: "/api/character/:_id", path: "/api/character/:_id",
where: "server" where: "server"
}).delete( }).delete(function () {
function () { const key = startPOSTResponse(this);
this.response.setHeader("Content-Type", "application/json"); const charId = this.params._id;
const header = this.request.headers; Meteor.call("deleteCharacter", key, charId, (err, res) => {
const key = header && header['authorization']; if (err) {
const charId = this.params._id; this.response.writeHead(err.error, err.reason);
ifKeyValid(key, this.response, "deleteCharacter", () => { this.response.end(err.details);
if (isOwner(charId, userIdFromKey(key))) { } else {
let error; this.response.end(JSON.stringify(res));
Characters.remove({_id: charId}, (err) => { }
if (err) });
error = err.message; });
});
if (error) {
this.response.writeHead(400, "Failed to delete character");
this.response.end(JSON.stringify({err: error}));
} else {
this.response.end(JSON.stringify({success: true}));
}
} else {
this.response.writeHead(403, "You do not have permission to delete this character");
this.response.end();
}
});
}
);
this.route("transferCharacterOwnership", { // PUT /api/character/:_id/owner this.route("transferCharacterOwnership", { // PUT /api/character/:_id/owner
path: "/api/character/:_id/owner", path: "/api/character/:_id/owner",
where: "server" where: "server"
}).put( }).put(function () {
function () { const key = startPOSTResponse(this);
this.response.setHeader("Content-Type", "application/json"); const charId = this.params._id;
const header = this.request.headers; const ownerId = this.request.body['id'];
const key = header && header['authorization']; Meteor.call("transferCharacterOwnership", key, charId, ownerId, (err, res) => {
const charId = this.params._id; if (err) {
ifKeyValid(key, this.response, "transferCharacterOwnership", () => { this.response.writeHead(err.error, err.reason);
if (isOwner(charId, userIdFromKey(key))) { this.response.end(err.details);
const newOwner = this.request.body['id']; } else {
let error; this.response.end(JSON.stringify(res));
Characters.update({_id: charId}, {"$set": {owner: newOwner}}, null, }
(err) => { });
if (err) });
error = err.message;
});
if (error) {
this.response.writeHead(400, "Failed to update character");
this.response.end(JSON.stringify({err: error}));
} else {
this.response.end(JSON.stringify({success: true}));
}
} else {
this.response.writeHead(403, "You do not have permission to transfer this character");
this.response.end();
}
});
}
);
this.route("insertFeatures", { // POST /api/character/:_id/feature this.route("insertFeatures", { // POST /api/character/:_id/feature
path: "/api/character/:_id/feature", path: "/api/character/:_id/feature",
where: "server", where: "server",
}).post( }).post(function () {
function () { const key = startPOSTResponse(this);
ifPostOK(this, "insertFeatures", () => { const charId = this.params._id;
const features = this.request.body; const features = this.request.body;
const charId = this.params._id; Meteor.call("insertFeatures", key, charId, features, (err, res) => {
let ids = []; if (err) {
let error; this.response.writeHead(err.error, err.reason);
for (let feature of features) { this.response.end(err.details);
feature.charId = charId; } else {
let id = Features.insert(feature, (err) => { this.response.end(JSON.stringify(res));
if (err) { }
error = err.message; });
} });
});
if (error)
break;
ids.push(id);
}
if (error) {
this.response.writeHead(400, "Failed to insert one or more features");
this.response.end(JSON.stringify({err: error, inserted: ids}));
} else {
this.response.end(JSON.stringify(ids));
}
});
}
);
this.route("insertProfs", { // POST /api/character/:_id/prof this.route("insertProfs", { // POST /api/character/:_id/prof
path: "/api/character/:_id/prof", path: "/api/character/:_id/prof",
where: "server", where: "server",
}).post( }).post(function () {
function () { const key = startPOSTResponse(this);
ifPostOK(this, "insertProfs", () => { const charId = this.params._id;
const profs = this.request.body; const profs = this.request.body;
const charId = this.params._id; Meteor.call("insertProfs", key, charId, profs, (err, res) => {
let ids = []; if (err) {
let error; this.response.writeHead(err.error, err.reason);
for (let prof of profs) { this.response.end(err.details);
prof.charId = charId; // we currently rely on the client to supply parent } else {
let id = Proficiencies.insert(prof, (err) => { this.response.end(JSON.stringify(res));
if (err) { }
error = err.message; });
} });
});
if (error)
break;
ids.push(id);
}
if (error) {
this.response.writeHead(400, "Failed to insert one or more profs");
this.response.end(JSON.stringify({err: error, inserted: ids}));
} else {
this.response.end(JSON.stringify(ids));
}
});
}
);
this.route("insertEffects", { // POST /api/character/:_id/effect this.route("insertEffects", { // POST /api/character/:_id/effect
path: "/api/character/:_id/effect", path: "/api/character/:_id/effect",
where: "server", where: "server",
}).post( }).post(function () {
function () { const key = startPOSTResponse(this);
ifPostOK(this, "insertEffects", () => { const charId = this.params._id;
const effects = this.request.body; const effects = this.request.body;
const charId = this.params._id; Meteor.call("insertEffects", key, charId, effects, (err, res) => {
let ids = []; if (err) {
let error; this.response.writeHead(err.error, err.reason);
for (let effect of effects) { this.response.end(err.details);
effect.charId = charId; // we currently rely on the client to supply parent } else {
let id = Effects.insert(effect, (err) => { this.response.end(JSON.stringify(res));
if (err) { }
error = err.message; });
} });
});
if (error)
break;
ids.push(id);
}
if (error) {
this.response.writeHead(400, "Failed to insert one or more effects");
this.response.end(JSON.stringify({err: error, inserted: ids}));
} else {
this.response.end(JSON.stringify(ids));
}
});
}
);
this.route("insertClasses", { // POST /api/character/:_id/class this.route("insertClasses", { // POST /api/character/:_id/class
path: "/api/character/:_id/class", path: "/api/character/:_id/class",
where: "server", where: "server",
}).post( }).post(function () {
function () { const key = startPOSTResponse(this);
ifPostOK(this, "insertClasses", () => { const charId = this.params._id;
const klasses = this.request.body; const classes = this.request.body;
const charId = this.params._id; Meteor.call("insertClasses", key, charId, classes, (err, res) => {
let ids = []; if (err) {
let error; this.response.writeHead(err.error, err.reason);
for (let klass of klasses) { this.response.end(err.details);
klass.charId = charId; // we currently rely on the client to supply parent } else {
let id = Classes.insert(klass, (err) => { this.response.end(JSON.stringify(res));
if (err) { }
error = err.message; });
} });
});
if (error)
break;
ids.push(id);
}
if (error) {
this.response.writeHead(400, "Failed to insert one or more classes");
this.response.end(JSON.stringify({err: error, inserted: ids}));
} else {
this.response.end(JSON.stringify(ids));
}
});
}
);
}); });
var ifPostOK = function (router, endpoint, callback) { const startPOSTResponse = function (request) {
router.response.setHeader("Content-Type", "application/json"); request.response.setHeader("Content-Type", "application/json");
var header = router.request.headers; const header = request.request.headers;
var key = header && header['authorization']; return header && header['authorization'];
ifKeyValid(key, router.response, endpoint, () => {
if (canEditCharacter(router.params._id, userIdFromKey(key))) {
callback();
} else {
router.response.writeHead(403, "You do not have permission to edit this character");
router.response.end();
}
}
);
}; };
var ifKeyValid = function (apiKey, response, method, callback) { var ifKeyValid = function (apiKey, response, method, callback) {
@@ -349,19 +227,19 @@ var ifKeyValid = function (apiKey, response, method, callback) {
} }
}; };
var isKeyValid = function (apiKey) { isKeyValid = function (apiKey) {
var user = Meteor.users.findOne({apiKey}); var user = Meteor.users.findOne({apiKey});
if (!user) return false; if (!user) return false;
var blackListed = Blacklist.findOne({userId: user._id}); var blackListed = Blacklist.findOne({userId: user._id});
return !blackListed; return !blackListed;
}; };
var userIdFromKey = function (apiKey) { userIdFromKey = function (apiKey) {
var user = Meteor.users.findOne({apiKey}); // we know user exists from isKeyValid var user = Meteor.users.findOne({apiKey}); // we know user exists from isKeyValid
return user._id; return user._id;
}; };
var rateLimiter = new RateLimiter(); rateLimiter = new RateLimiter();
// global limit // global limit
rateLimiter.addRule({apiKey: String}, 10, 1000); rateLimiter.addRule({apiKey: String}, 10, 1000);
@@ -380,7 +258,7 @@ rateLimiter.addRule({apiKey: String, method: "insertProfs"}, 5, 5000);
rateLimiter.addRule({apiKey: String, method: "insertEffects"}, 5, 5000); rateLimiter.addRule({apiKey: String, method: "insertEffects"}, 5, 5000);
rateLimiter.addRule({apiKey: String, method: "insertClasses"}, 5, 5000); rateLimiter.addRule({apiKey: String, method: "insertClasses"}, 5, 5000);
var isRateLimited = function (apiKey, method) { isRateLimited = function (apiKey, method) {
const limited = !rateLimiter.check({apiKey: apiKey, method: method}).allowed; const limited = !rateLimiter.check({apiKey: apiKey, method: method}).allowed;
if (limited) { if (limited) {
console.log(`Rate limit hit by API key ${apiKey}`); console.log(`Rate limit hit by API key ${apiKey}`);

241
app/lib/functions/api.js Normal file
View File

@@ -0,0 +1,241 @@
/**
* @return {string}
*/
JSONExport = function (charId) {
const character = {
"attacks": Attacks.find({charId: charId}).fetch(),
"characters": Characters.find({_id: charId}).fetch(),
"classes": Classes.find({charId: charId}).fetch(),
"containers": Containers.find({charId: charId}).fetch(),
"effects": Effects.find({charId: charId}).fetch(),
"experience": Experiences.find({charId: charId}).fetch(),
"features": Features.find({charId: charId}).fetch(),
"items": Items.find({charId: charId}).fetch(),
"notes": Notes.find({charId: charId}).fetch(),
"proficiencies": Proficiencies.find({charId: charId}).fetch(),
"spellLists": SpellLists.find({charId: charId}).fetch(),
"spells": Spells.find({charId: charId}).fetch()
};
return JSON.stringify(character);
};
Meteor.methods({
"insertSpells": function (key, charId, listId, spells) {
if (Meteor.isClient) return;
ifCanEdit(key, charId, "addSpellsToCharacter", () => {
let ids = [];
let error;
for (let spell of spells) {
spell.parent = {id: listId, collection: "SpellLists"};
spell.charId = charId;
let id = Spells.insert(spell, (err) => {
if (err) {
error = err.message;
}
});
ids.push(id);
}
if (error) {
throw new Meteor.Error(400, "Failed to insert one or more spells", JSON.stringify({
err: error,
inserted: ids
}));
} else {
return ids;
}
});
},
"insertCharacter": function (key, character) {
if (Meteor.isClient) return;
ifAuthorized(key, "createCharacter", () => {
let error;
character.owner = userIdFromKey(key);
let id = Characters.insert(character, (err) => {
if (err)
error = err.message;
});
if (error) {
throw new Meteor.Error(400, "Failed to insert character", JSON.stringify({err: error}));
} else {
return {id: id};
}
});
},
"deleteCharacter": function (key, charId) {
if (Meteor.isClient) return;
ifAuthorized(key, "deleteCharacter", () => {
if (isOwner(charId, userIdFromKey(key))) {
let error;
Characters.remove({_id: charId}, (err) => {
if (err)
error = err.message;
});
if (error) {
throw new Meteor.Error(400, "Failed to delete character", JSON.stringify({err: error}));
} else {
return {success: true};
}
} else {
throw new Meteor.Error(403, "You do not have permission to delete the requested character");
}
});
},
"transferCharacterOwnership": function (key, charId, newOwner) {
if (Meteor.isClient) return;
ifAuthorized(key, "transferCharacterOwnership", () => {
if (isOwner(charId, userIdFromKey(key))) {
let error;
Characters.update({_id: charId}, {"$set": {owner: newOwner}}, null,
(err) => {
if (err)
error = err.message;
});
if (error) {
throw new Meteor.Error(400, "Failed to update character", JSON.stringify({err: error}));
} else {
return {success: true};
}
} else {
throw new Meteor.Error(403, "You do not have permission to transfer the requested character");
}
});
},
"insertFeatures": function (key, charId, features) {
if (Meteor.isClient) return;
ifCanEdit(key, charId, "insertFeatures", () => {
let ids = [];
let error;
for (let feature of features) {
feature.charId = charId;
let id = Features.insert(feature, (err) => {
if (err) {
error = err.message;
}
});
if (error)
break;
ids.push(id);
}
if (error) {
throw new Meteor.Error(400, "Failed to insert one or more features", JSON.stringify({
err: error,
inserted: ids
}));
} else {
return ids;
}
});
},
"insertProfs": function (key, charId, profs) {
if (Meteor.isClient) return;
ifCanEdit(key, charId, "insertProfs", () => {
let ids = [];
let error;
for (let prof of profs) {
prof.charId = charId; // we currently rely on the client to supply parent
let id = Proficiencies.insert(prof, (err) => {
if (err) {
error = err.message;
}
});
if (error)
break;
ids.push(id);
}
if (error) {
throw new Meteor.Error(400, "Failed to insert one or more profs", JSON.stringify({
err: error,
inserted: ids
}));
} else {
return ids;
}
});
},
"insertEffects": function (key, charId, effects) {
if (Meteor.isClient) return;
ifCanEdit(key, charId, "insertEffects", () => {
let ids = [];
let error;
for (let effect of effects) {
effect.charId = charId; // we currently rely on the client to supply parent
let id = Effects.insert(effect, (err) => {
if (err) {
error = err.message;
}
});
if (error)
break;
ids.push(id);
}
if (error) {
throw new Meteor.Error(400, "Failed to insert one or more effects", JSON.stringify({
err: error,
inserted: ids
}));
} else {
return ids;
}
});
},
"insertClasses": function (key, charId, klasses) {
if (Meteor.isClient) return;
ifCanEdit(key, charId, "insertClasses", () => {
let ids = [];
let error;
for (let klass of klasses) {
klass.charId = charId; // we currently rely on the client to supply parent
let id = Classes.insert(klass, (err) => {
if (err) {
error = err.message;
}
});
if (error)
break;
ids.push(id);
}
if (error) {
throw new Meteor.Error(400, "Failed to insert one or more classes", JSON.stringify({
err: error,
inserted: ids
}));
} else {
return ids;
}
});
}
});
var ifCanEdit = function (key, charId, method, callback) {
if (canEditCharacter(charId, userIdFromKey(key))) {
ifAuthorized(key, method, callback);
} else {
throw new Meteor.Error(403, "You do not have permission to edit the requested character");
}
};
var ifAuthorized = function (apiKey, method, callback) {
if (!apiKey) {
throw new Meteor.Error(403, "You must use an api key to access this api");
} else if (!isKeyValid(apiKey)) {
throw new Meteor.Error(403, "API key is invalid");
} else if (isRateLimited(apiKey, method)) {
throw new Meteor.Error(429, "Too many requests", JSON.stringify({
"timeToReset": rateLimiter.check({apiKey: apiKey, method: method}).timeToReset
}));
} else {
rateLimiter.increment({apiKey: apiKey, method: method});
callback();
}
};

View File

@@ -1,17 +0,0 @@
JSONExport = function(charId) {
var character = {
"attacks": Attacks.find({charId: charId}).fetch(),
"characters": Characters.find({_id: charId}).fetch(),
"classes": Classes.find({charId: charId}).fetch(),
"containers": Containers.find({charId: charId}).fetch(),
"effects": Effects.find({charId: charId}).fetch(),
"experience": Experiences.find({charId: charId}).fetch(),
"features": Features.find({charId: charId}).fetch(),
"items": Items.find({charId: charId}).fetch(),
"notes": Notes.find({charId: charId}).fetch(),
"proficiencies": Proficiencies.find({charId: charId}).fetch(),
"spellLists": SpellLists.find({charId: charId}).fetch(),
"spells": Spells.find({charId: charId}).fetch()
};
return JSON.stringify(character);
}