Merge pull request #202 from mommothazaz123/master
Add multiple new API endpoints
This commit is contained in:
1
app/.gitignore
vendored
1
app/.gitignore
vendored
@@ -8,4 +8,5 @@ private/oldClient
|
||||
nohup.out
|
||||
node_modules
|
||||
dump
|
||||
.idea/
|
||||
.cache
|
||||
|
||||
@@ -1,91 +1,268 @@
|
||||
Router.map(function() {
|
||||
this.route("vmixCharacter", {
|
||||
path: "/vmix-character/:_id/",
|
||||
where: "server",
|
||||
action: function() {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
ifKeyValid(key, this.response, "vmixCharacter", () =>
|
||||
this.response.end(vMixCharacter(this.params._id))
|
||||
);
|
||||
},
|
||||
});
|
||||
this.route("vmixParty", {
|
||||
path: "/vmix-party/:_id/",
|
||||
where: "server",
|
||||
action: function() {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
ifKeyValid(key, this.response, "vmixParty", () =>
|
||||
this.response.end(vMixParty(this.params._id))
|
||||
);
|
||||
},
|
||||
});
|
||||
Router.map(function () {
|
||||
this.route("vmixCharacter", {
|
||||
path: "/vmix-character/:_id/",
|
||||
where: "server",
|
||||
action: function () {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
ifKeyValid(key, this.response, "vmixCharacter", () =>
|
||||
this.response.end(vMixCharacter(this.params._id))
|
||||
);
|
||||
},
|
||||
});
|
||||
this.route("vmixParty", {
|
||||
path: "/vmix-party/:_id/",
|
||||
where: "server",
|
||||
action: function () {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
ifKeyValid(key, this.response, "vmixParty", () =>
|
||||
this.response.end(vMixParty(this.params._id))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.route("jsonCharacterSheet", {
|
||||
path: "/character/:_id/json",
|
||||
where: "server",
|
||||
action: function() {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
ifKeyValid(key, this.response, "jsonCharacterSheet", () => {
|
||||
if (canViewCharacter(this.params._id, userIdFromKey(key))){
|
||||
this.response.end(JSONExport(this.params._id))
|
||||
} else {
|
||||
this.response.writeHead(403, "You do not have permission to view this character");
|
||||
this.response.end();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
this.route("jsonCharacterSheet", { // GET /character/:_id/json?key=:key
|
||||
path: "/character/:_id/json",
|
||||
where: "server",
|
||||
action: function () {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
ifKeyValid(key, this.response, "jsonCharacterSheet", () => {
|
||||
if (canViewCharacter(this.params._id, userIdFromKey(key))) {
|
||||
this.response.end(JSONExport(this.params._id))
|
||||
} else {
|
||||
this.response.writeHead(403, "You do not have permission to view this character");
|
||||
this.response.end();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.route("getUserId", { // GET /api/user?username=:un&key=:key
|
||||
path: "/api/user",
|
||||
where: "server",
|
||||
action: function () {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
var query = this.params.query;
|
||||
var key = query && query.key;
|
||||
var username = query && query.username;
|
||||
ifKeyValid(key, this.response, "getUserId", () => {
|
||||
Meteor.call("getUserId", username, (err, result) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
this.response.writeHead(404, "User not found");
|
||||
this.response.end();
|
||||
} else {
|
||||
console.log(result);
|
||||
this.response.end(JSON.stringify({id: result}));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.route("addSpellsToCharacter", { // POST /api/character/:_id/spellList/:listId
|
||||
path: "/api/character/:_id/spellList/:listId",
|
||||
where: "server"
|
||||
}).post(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const spells = this.request.body;
|
||||
const charId = this.params._id;
|
||||
const listId = this.params.listId;
|
||||
Meteor.call("insertSpells", key, charId, listId, spells, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("createCharacter", { // POST /api/character
|
||||
path: "/api/character",
|
||||
where: "server"
|
||||
}).post(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const character = this.request.body;
|
||||
Meteor.call("insertCharacter", key, character, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("deleteCharacter", { // DELETE /api/character/:_id
|
||||
path: "/api/character/:_id",
|
||||
where: "server"
|
||||
}).delete(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const charId = this.params._id;
|
||||
Meteor.call("deleteCharacter", key, charId, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("transferCharacterOwnership", { // PUT /api/character/:_id/owner
|
||||
path: "/api/character/:_id/owner",
|
||||
where: "server"
|
||||
}).put(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const charId = this.params._id;
|
||||
const ownerId = this.request.body['id'];
|
||||
Meteor.call("transferCharacterOwnership", key, charId, ownerId, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("insertFeatures", { // POST /api/character/:_id/feature
|
||||
path: "/api/character/:_id/feature",
|
||||
where: "server",
|
||||
}).post(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const charId = this.params._id;
|
||||
const features = this.request.body;
|
||||
Meteor.call("insertFeatures", key, charId, features, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("insertProfs", { // POST /api/character/:_id/prof
|
||||
path: "/api/character/:_id/prof",
|
||||
where: "server",
|
||||
}).post(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const charId = this.params._id;
|
||||
const profs = this.request.body;
|
||||
Meteor.call("insertProfs", key, charId, profs, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("insertEffects", { // POST /api/character/:_id/effect
|
||||
path: "/api/character/:_id/effect",
|
||||
where: "server",
|
||||
}).post(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const charId = this.params._id;
|
||||
const effects = this.request.body;
|
||||
Meteor.call("insertEffects", key, charId, effects, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.route("insertClasses", { // POST /api/character/:_id/class
|
||||
path: "/api/character/:_id/class",
|
||||
where: "server",
|
||||
}).post(function () {
|
||||
const key = startPOSTResponse(this);
|
||||
const charId = this.params._id;
|
||||
const classes = this.request.body;
|
||||
Meteor.call("insertClasses", key, charId, classes, (err, res) => {
|
||||
if (err) {
|
||||
this.response.writeHead(err.error, err.reason);
|
||||
this.response.end(err.details);
|
||||
} else {
|
||||
this.response.end(JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var ifKeyValid = function(apiKey, response, method, callback){
|
||||
if (!apiKey){
|
||||
response.writeHead(403, "You must use an api key to access this api");
|
||||
response.end();
|
||||
} else if (!isKeyValid(apiKey)){
|
||||
response.writeHead(403, "API key is invalid");
|
||||
response.end();
|
||||
} else if (isRateLimited(apiKey, method)){
|
||||
response.writeHead(429, "Too many requests");
|
||||
response.end(JSON.stringify({
|
||||
"timeToReset": rateLimiter.check({apiKey: apiKey, method: method}).timeToReset
|
||||
}));
|
||||
} else {
|
||||
rateLimiter.increment({apiKey: apiKey, method: method})
|
||||
callback();
|
||||
}
|
||||
const startPOSTResponse = function (request) {
|
||||
request.response.setHeader("Content-Type", "application/json");
|
||||
const header = request.request.headers;
|
||||
return header && header['authorization'];
|
||||
};
|
||||
|
||||
var isKeyValid = function(apiKey){
|
||||
var user = Meteor.users.findOne({apiKey});
|
||||
if (!user) return false;
|
||||
var blackListed = Blacklist.findOne({userId: user._id});
|
||||
return !blackListed;
|
||||
var ifKeyValid = function (apiKey, response, method, callback) {
|
||||
if (!apiKey) {
|
||||
response.writeHead(403, "You must use an api key to access this api");
|
||||
response.end();
|
||||
} else if (!isKeyValid(apiKey)) {
|
||||
response.writeHead(403, "API key is invalid");
|
||||
response.end();
|
||||
} else if (isRateLimited(apiKey, method)) {
|
||||
response.writeHead(429, "Too many requests");
|
||||
response.end(JSON.stringify({
|
||||
"timeToReset": rateLimiter.check({apiKey: apiKey, method: method}).timeToReset
|
||||
}));
|
||||
} else {
|
||||
rateLimiter.increment({apiKey: apiKey, method: method});
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
var userIdFromKey = function(apiKey){
|
||||
var user = Meteor.users.findOne({apiKey}); // we know user exists from isKeyValid
|
||||
return user._id;
|
||||
}
|
||||
isKeyValid = function (apiKey) {
|
||||
var user = Meteor.users.findOne({apiKey});
|
||||
if (!user) return false;
|
||||
var blackListed = Blacklist.findOne({userId: user._id});
|
||||
return !blackListed;
|
||||
};
|
||||
|
||||
var rateLimiter = new RateLimiter();
|
||||
rateLimiter.addRule({apiKey: String}, 5, 5000);
|
||||
userIdFromKey = function (apiKey) {
|
||||
var user = Meteor.users.findOne({apiKey}); // we know user exists from isKeyValid
|
||||
return user._id;
|
||||
};
|
||||
|
||||
rateLimiter = new RateLimiter();
|
||||
// global limit
|
||||
rateLimiter.addRule({apiKey: String}, 10, 1000);
|
||||
|
||||
// vmix stuff
|
||||
rateLimiter.addRule({apiKey: String, method: "vmixCharacter"}, 2, 10000);
|
||||
rateLimiter.addRule({apiKey: String, method: "vmixParty"}, 2, 10000);
|
||||
rateLimiter.addRule({apiKey: String, method: "jsonCharacterSheet"}, 5, 5000);
|
||||
|
||||
var isRateLimited = function(apiKey, method){
|
||||
const limited = !rateLimiter.check({apiKey: apiKey, method: method}).allowed
|
||||
if (limited) {
|
||||
console.log(`Rate limit hit by API key ${apiKey}`);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// bot API endpoints
|
||||
rateLimiter.addRule({apiKey: String, method: "jsonCharacterSheet"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "getUserId"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "addSpellsToCharacter"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "createCharacter"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "transferCharacterOwnership"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "insertFeatures"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "insertProfs"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "insertEffects"}, 5, 5000);
|
||||
rateLimiter.addRule({apiKey: String, method: "insertClasses"}, 5, 5000);
|
||||
|
||||
isRateLimited = function (apiKey, method) {
|
||||
const limited = !rateLimiter.check({apiKey: apiKey, method: method}).allowed;
|
||||
if (limited) {
|
||||
console.log(`Rate limit hit by API key ${apiKey}`);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
471
app/lib/functions/api.js
Normal file
471
app/lib/functions/api.js
Normal file
@@ -0,0 +1,471 @@
|
||||
/**
|
||||
* @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;
|
||||
assertCanEdit(key, charId, "addSpellsToCharacter");
|
||||
let ids = [];
|
||||
let error;
|
||||
for (let spell of spells) {
|
||||
spell.charId = charId;
|
||||
try {
|
||||
Schemas.Spell.clean(spell);
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
error = e.error;
|
||||
}
|
||||
if (!error) {
|
||||
spell.parent = {id: listId, collection: "SpellLists"};
|
||||
let id = Spells.direct.insert(spell, (err) => {
|
||||
if (err) {
|
||||
error = err.message;
|
||||
}
|
||||
});
|
||||
// console.log(id);
|
||||
ids.push(id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
assertAuthorized(key, "createCharacter");
|
||||
let error, id;
|
||||
|
||||
character.owner = userIdFromKey(key);
|
||||
try {
|
||||
Schemas.Character.clean(character);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
error = e.error;
|
||||
}
|
||||
if (!error) {
|
||||
id = Characters.direct.insert(character, (err) => {
|
||||
if (err)
|
||||
error = err.message;
|
||||
});
|
||||
afterCharacterInsert(id);
|
||||
return {id: id};
|
||||
} else {
|
||||
throw new Meteor.Error(400, "Failed to insert character", JSON.stringify({err: error}));
|
||||
}
|
||||
},
|
||||
|
||||
"deleteCharacter": function (key, charId) {
|
||||
if (Meteor.isClient) return;
|
||||
assertAuthorized(key, "deleteCharacter");
|
||||
if (isOwner(charId, userIdFromKey(key))) {
|
||||
let error;
|
||||
|
||||
Characters.direct.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;
|
||||
assertAuthorized(key, "transferCharacterOwnership");
|
||||
if (isOwner(charId, userIdFromKey(key))) {
|
||||
let error;
|
||||
Characters.direct.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;
|
||||
assertCanEdit(key, charId, "insertFeatures");
|
||||
let ids = [];
|
||||
let error;
|
||||
for (let feature of features) {
|
||||
feature.charId = charId;
|
||||
try {
|
||||
Schemas.Feature.clean(feature);
|
||||
} catch (e) {
|
||||
error = e.error;
|
||||
}
|
||||
if (!error) {
|
||||
let id = Features.direct.insert(feature, (err) => {
|
||||
if (err) {
|
||||
error = err.message;
|
||||
}
|
||||
});
|
||||
ids.push(id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
assertCanEdit(key, charId, "insertProfs");
|
||||
let ids = [];
|
||||
let error;
|
||||
for (let prof of profs) {
|
||||
prof.charId = charId;
|
||||
try {
|
||||
Schemas.Proficiency.clean(prof, {filter: false});
|
||||
} catch (e) {
|
||||
error = e.error;
|
||||
}
|
||||
if (!error) {
|
||||
let id = Proficiencies.direct.insert(prof, (err) => {
|
||||
if (err) {
|
||||
error = err.message;
|
||||
}
|
||||
});
|
||||
ids.push(id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
assertCanEdit(key, charId, "insertEffects");
|
||||
let ids = [];
|
||||
let error;
|
||||
for (let effect of effects) {
|
||||
effect.charId = charId;
|
||||
try {
|
||||
Schemas.Effect.clean(effect, {filter: false});
|
||||
} catch (e) {
|
||||
error = e.error;
|
||||
}
|
||||
if (!error) {
|
||||
let id = Effects.direct.insert(effect, (err) => {
|
||||
if (err) {
|
||||
error = err.message;
|
||||
}
|
||||
});
|
||||
ids.push(id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
assertCanEdit(key, charId, "insertClasses");
|
||||
let ids = [];
|
||||
let error;
|
||||
for (let klass of klasses) {
|
||||
klass.charId = charId;
|
||||
try {
|
||||
Schemas.Class.clean(klass);
|
||||
} catch (e) {
|
||||
error = e.error;
|
||||
}
|
||||
if (!error) {
|
||||
let id = Classes.direct.insert(klass, (err) => {
|
||||
if (err) {
|
||||
error = err.message;
|
||||
}
|
||||
});
|
||||
ids.push(id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
throw new Meteor.Error(400, "Failed to insert one or more classes", JSON.stringify({
|
||||
err: error,
|
||||
inserted: ids
|
||||
}));
|
||||
} else {
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var assertCanEdit = function (key, charId, method) {
|
||||
if (canEditCharacter(charId, userIdFromKey(key))) {
|
||||
assertAuthorized(key, method);
|
||||
} else {
|
||||
throw new Meteor.Error(403, "You do not have permission to edit the requested character");
|
||||
}
|
||||
};
|
||||
|
||||
var assertAuthorized = function (apiKey, method) {
|
||||
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});
|
||||
}
|
||||
};
|
||||
|
||||
var afterCharacterInsert = function (charId) {
|
||||
// Effects
|
||||
Effects.direct.insert({
|
||||
charId: charId,
|
||||
name: "Constitution modifier for each level",
|
||||
stat: "hitPoints",
|
||||
operation: "add",
|
||||
calculation: "level * constitutionMod",
|
||||
parent: {
|
||||
id: charId,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.direct.insert({
|
||||
charId: charId,
|
||||
name: "Proficiency bonus by level",
|
||||
stat: "proficiencyBonus",
|
||||
operation: "add",
|
||||
calculation: "floor(level / 4 + 1.75)",
|
||||
parent: {
|
||||
id: charId,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.direct.insert({
|
||||
charId: charId,
|
||||
name: "Dexterity Armor Bonus",
|
||||
stat: "armor",
|
||||
operation: "add",
|
||||
calculation: "dexterityArmor",
|
||||
parent: {
|
||||
id: charId,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.direct.insert({
|
||||
charId: charId,
|
||||
name: "Natural Armor",
|
||||
stat: "armor",
|
||||
operation: "base",
|
||||
value: 10,
|
||||
parent: {
|
||||
id: charId,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.direct.insert({
|
||||
charId: charId,
|
||||
name: "Natural Carrying Capacity",
|
||||
stat: "carryMultiplier",
|
||||
operation: "base",
|
||||
value: "1",
|
||||
parent: {
|
||||
id: charId,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
// Features
|
||||
let featureId = Features.direct.insert({
|
||||
name: "Base Ability Scores",
|
||||
charId: charId,
|
||||
enabled: true,
|
||||
alwaysEnabled: true,
|
||||
});
|
||||
Effects.direct.insert({
|
||||
stat: "strength",
|
||||
charId: charId,
|
||||
parent: {
|
||||
id: featureId,
|
||||
collection: "Features",
|
||||
},
|
||||
operation: "base",
|
||||
value: 10,
|
||||
enabled: true,
|
||||
});
|
||||
Effects.direct.insert({
|
||||
stat: "dexterity",
|
||||
charId: charId,
|
||||
parent: {
|
||||
id: featureId,
|
||||
collection: "Features",
|
||||
},
|
||||
operation: "base",
|
||||
value: 10,
|
||||
enabled: true,
|
||||
});
|
||||
Effects.direct.insert({
|
||||
stat: "constitution",
|
||||
charId: charId,
|
||||
parent: {
|
||||
id: featureId,
|
||||
collection: "Features",
|
||||
},
|
||||
operation: "base",
|
||||
value: 10,
|
||||
enabled: true,
|
||||
});
|
||||
Effects.direct.insert({
|
||||
stat: "intelligence",
|
||||
charId: charId,
|
||||
parent: {
|
||||
id: featureId,
|
||||
collection: "Features",
|
||||
},
|
||||
operation: "base",
|
||||
value: 10,
|
||||
enabled: true,
|
||||
});
|
||||
Effects.direct.insert({
|
||||
stat: "wisdom",
|
||||
charId: charId,
|
||||
parent: {
|
||||
id: featureId,
|
||||
collection: "Features",
|
||||
},
|
||||
operation: "base",
|
||||
value: 10,
|
||||
enabled: true,
|
||||
});
|
||||
Effects.direct.insert({
|
||||
stat: "charisma",
|
||||
charId: charId,
|
||||
parent: {
|
||||
id: featureId,
|
||||
collection: "Features",
|
||||
},
|
||||
operation: "base",
|
||||
value: 10,
|
||||
enabled: true,
|
||||
});
|
||||
// Items
|
||||
let containerId = Containers.direct.insert({
|
||||
name: "Coin Pouch",
|
||||
charId: charId,
|
||||
isCarried: true,
|
||||
description: "A sturdy pouch for coins",
|
||||
color: "d",
|
||||
});
|
||||
Items.direct.insert({
|
||||
name: "Gold piece",
|
||||
plural: "Gold pieces",
|
||||
charId: charId,
|
||||
quantity: 0,
|
||||
weight: 0.02,
|
||||
value: 1,
|
||||
color: "n",
|
||||
parent: {
|
||||
id: containerId,
|
||||
collection: "Containers",
|
||||
},
|
||||
settings: {
|
||||
showIncrement: true,
|
||||
},
|
||||
});
|
||||
Items.direct.insert({
|
||||
name: "Silver piece",
|
||||
plural: "Silver pieces",
|
||||
charId: charId,
|
||||
quantity: 0,
|
||||
weight: 0.02,
|
||||
value: 0.1,
|
||||
color: "q",
|
||||
parent: {
|
||||
id: containerId,
|
||||
collection: "Containers",
|
||||
},
|
||||
settings: {
|
||||
showIncrement: true,
|
||||
},
|
||||
});
|
||||
Items.direct.insert({
|
||||
name: "Copper piece",
|
||||
plural: "Copper pieces",
|
||||
charId: charId,
|
||||
quantity: 0,
|
||||
weight: 0.02,
|
||||
value: 0.01,
|
||||
color: "s",
|
||||
parent: {
|
||||
id: containerId,
|
||||
collection: "Containers",
|
||||
},
|
||||
settings: {
|
||||
showIncrement: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
isOwner = function(charId, userId) {
|
||||
userId = userId || Meteor.userId();
|
||||
var char = Characters.findOne(charId, {fields: {owner: 1}});
|
||||
if (!char) return true;
|
||||
return (userId === char.owner);
|
||||
};
|
||||
|
||||
canEditCharacter = function(charId, userId){
|
||||
userId = userId || Meteor.userId();
|
||||
var char = Characters.findOne(charId, {fields: {owner: 1, writers: 1}});
|
||||
|
||||
Reference in New Issue
Block a user