Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ce1b6aff8 | ||
|
|
41731212ef | ||
|
|
ef9867d409 | ||
|
|
721300700e | ||
|
|
bc6dfbe498 | ||
|
|
0a22073d67 | ||
|
|
857213f157 | ||
|
|
b3371fca53 | ||
|
|
3fbb006783 | ||
|
|
2253672f43 | ||
|
|
ed6d557f8a | ||
|
|
4d642b56bb | ||
|
|
436c5bb785 | ||
|
|
8489ef5ec0 | ||
|
|
c9710bdb09 | ||
|
|
26784f11b6 | ||
|
|
23d43f7d43 | ||
|
|
1ebb0d2527 | ||
|
|
9d86cb8bee | ||
|
|
3343f8a813 | ||
|
|
0260824c2f | ||
|
|
66ee3ff808 | ||
|
|
cb71f6d380 | ||
|
|
2f04d9ec1c | ||
|
|
40c54524a7 | ||
|
|
b890a3b11e | ||
|
|
c9242a95f3 | ||
|
|
fedda62c7c | ||
|
|
612575d0e6 | ||
|
|
d1d22c0d89 | ||
|
|
b94f5ebb4b | ||
|
|
3f32535666 | ||
|
|
4ea02c4fbb | ||
|
|
b052e8dd19 | ||
|
|
e2822b9f22 | ||
|
|
c46b836985 | ||
|
|
65d1bac0dc |
86
README.md
86
README.md
@@ -1,13 +1,93 @@
|
||||
RPG Docs
|
||||
DiceCloud
|
||||
========
|
||||
|
||||
This is the repo for [DiceCloud](dicecloud.com).
|
||||
|
||||
DiceCloud is a free, auditable, real-time character sheet for D&D 5e.
|
||||
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
Setting up your character on DiceCloud takes a little longer than
|
||||
just filling it in on a paper character sheet would. The goal of using an
|
||||
online sheet is to make actually playing the game more streamlined, and
|
||||
ultimately more fun. So putting a little extra effort into setting up a
|
||||
character now pays off over and over again once you're playing.
|
||||
|
||||
The idea is to track where each number comes from, and allow you to easily make
|
||||
changes on the fly. Let's look at a hypothetical example.
|
||||
|
||||
> You need to swim through a sunken section of dungeon to fetch the quest's Thing.
|
||||
> You'll need to take off your magical Plate Armor of +1 Constitution to swim
|
||||
> without sinking, of course.
|
||||
>
|
||||
> Taking it off will take away that disadvantage on
|
||||
> stealth checks, change your armor class, your speed and your constitution, and
|
||||
> which in turn changes your hit points and your constitution saving throw.
|
||||
> Working out all those changes in the middle of a game will drag the game to a
|
||||
> halt.
|
||||
>
|
||||
> Fortunately you have DiceCloud, so it's a matter of dragging
|
||||
> your Plate Armor +1 Con from your "equipment" box to your "backpack" box and
|
||||
> you're done. Your hitpoints change correctly, your saving throws are up to date,
|
||||
> your armor class goes back to reflecting the fact that you have natural armor
|
||||
> from being a dragonborn. Your character sheet keeps up and you
|
||||
> ultimately get more time to play the game. Huzzah!
|
||||
|
||||
Getting started
|
||||
---------------
|
||||
|
||||
`git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud`
|
||||
Running DiceCloud locally, either to run it locally, away from an internet
|
||||
connection, or to contribute to developing it further, is fairly
|
||||
straightforward and it should work on Linux, Windows, and Mac.
|
||||
|
||||
You'll need to have installed:
|
||||
|
||||
- [git](https://www.atlassian.com/git/tutorials/install-git)
|
||||
- [Meteor](https://www.meteor.com/install)
|
||||
|
||||
Then, it's just a matter of cloning this repository into a folder, installing the dependencies and running
|
||||
`meteor` in the app directory:
|
||||
|
||||
`git clone https://github.com/ThaumRystra/DiceCloud dicecloud`
|
||||
`cd dicecloud`
|
||||
`cd app`
|
||||
`bower install`
|
||||
`meteor npm install`
|
||||
`meteor`
|
||||
|
||||
If you edit the source code at this point, Meteor will rebuild the server with
|
||||
your changes.
|
||||
|
||||
If you want to simulate a production environment, run `meteor --production`
|
||||
|
||||
This will minimize all the files served to your browser, and load a lot faster,
|
||||
in exchange for not watching the source code for changes.
|
||||
|
||||
Note that this is not how you should deploy Meteor to your own web server, that
|
||||
is documented here: https://guide.meteor.com/deployment.html
|
||||
|
||||
After running `meteor` or `meteor --production`, you should see this, possibly
|
||||
mixed with other logged text:
|
||||
|
||||
```
|
||||
=> Started proxy.
|
||||
=> Started MongoDB.
|
||||
=> Started your app.
|
||||
|
||||
=> App running at: http://localhost:3000/
|
||||
```
|
||||
|
||||
Now, visiting http://localhost:3000/ should show you an empty instance of
|
||||
DiceCloud running.
|
||||
|
||||
To stop the process when you are done (or if it gets stuck) press `ctrl-c`
|
||||
|
||||
## Adding default documents
|
||||
|
||||
Navigate to `/dataSources/srd/srdimport.js`, and follow the steps under
|
||||
'First Setup', running the code in your browser's console, while logged in to
|
||||
your own instance of DiceCloud.
|
||||
|
||||
Do not run code in your browser console on the live version of DiceCloud hosted
|
||||
at dicecloud.com, as doing so could result in a large number of denied requests
|
||||
to the server, and may get your account permanently banned.
|
||||
|
||||
2
app/.gitignore
vendored
2
app/.gitignore
vendored
@@ -8,3 +8,5 @@ private/oldClient
|
||||
nohup.out
|
||||
node_modules
|
||||
dump
|
||||
.idea/
|
||||
.cache
|
||||
|
||||
@@ -27,7 +27,7 @@ meteor-base@1.4.0
|
||||
mobile-experience@1.0.5
|
||||
mongo@1.6.0
|
||||
blaze-html-templates
|
||||
session@1.1.8
|
||||
session@1.2.0
|
||||
jquery@1.11.10
|
||||
tracker@1.2.0
|
||||
logging@1.1.20
|
||||
@@ -36,13 +36,11 @@ ejson@1.1.0
|
||||
spacebars
|
||||
check@1.3.1
|
||||
useraccounts:iron-routing
|
||||
wizonesolutions:canonical
|
||||
standard-minifier-js@2.4.0
|
||||
shell-server@0.4.0
|
||||
seba:minifiers-autoprefixer
|
||||
nikogosovd:multiple-uihooks
|
||||
templates:array
|
||||
ecmascript@0.12.0
|
||||
ecmascript@0.12.4
|
||||
es5-shim@4.8.0
|
||||
differential:vulcanize
|
||||
reactive-dict@1.2.1
|
||||
@@ -54,3 +52,5 @@ ddp-rate-limiter@1.0.7
|
||||
rate-limit@1.0.9
|
||||
iron:router
|
||||
littledata:synced-cron
|
||||
montiapm:agent
|
||||
zodern:standard-minifier-js
|
||||
|
||||
@@ -1 +1 @@
|
||||
METEOR@1.8
|
||||
METEOR@1.8.0.2
|
||||
|
||||
@@ -3,15 +3,15 @@ accounts-google@1.3.2
|
||||
accounts-oauth@1.1.16
|
||||
accounts-password@1.5.1
|
||||
accounts-ui@1.3.1
|
||||
accounts-ui-unstyled@1.4.1
|
||||
accounts-ui-unstyled@1.4.2
|
||||
aldeed:collection2@2.10.0
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:schema-deny@1.1.0
|
||||
aldeed:schema-index@1.1.1
|
||||
aldeed:simple-schema@1.5.4
|
||||
allow-deny@1.1.0
|
||||
autoupdate@1.5.0
|
||||
babel-compiler@7.2.0
|
||||
autoupdate@1.5.1
|
||||
babel-compiler@7.2.4
|
||||
babel-runtime@1.3.0
|
||||
base64@1.0.11
|
||||
binary-heap@1.0.11
|
||||
@@ -19,7 +19,7 @@ blaze@2.3.3
|
||||
blaze-html-templates@1.1.2
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.6.0
|
||||
caching-compiler@1.2.0
|
||||
caching-compiler@1.2.1
|
||||
caching-html-compiler@1.1.3
|
||||
callback-hook@1.1.0
|
||||
check@1.3.1
|
||||
@@ -33,10 +33,10 @@ ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.2.0
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.0
|
||||
diff-sequence@1.1.1
|
||||
differential:vulcanize@3.0.0
|
||||
dynamic-import@0.5.0
|
||||
ecmascript@0.12.0
|
||||
dynamic-import@0.5.1
|
||||
ecmascript@0.12.6
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.8.0
|
||||
ecmascript-runtime-server@0.7.1
|
||||
@@ -44,14 +44,14 @@ ecwyne:mathjs@0.25.0
|
||||
ejson@1.1.0
|
||||
email@1.2.3
|
||||
es5-shim@4.8.0
|
||||
fetch@0.1.0
|
||||
fetch@0.1.1
|
||||
geojson-utils@1.0.10
|
||||
google-config-ui@1.0.1
|
||||
google-oauth@1.2.6
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.4.1
|
||||
http@1.4.2
|
||||
id-map@1.1.0
|
||||
inter-process-messaging@0.1.0
|
||||
iron:controller@1.0.12
|
||||
@@ -64,6 +64,7 @@ iron:router@1.1.2
|
||||
iron:url@1.1.0
|
||||
jquery@1.11.11
|
||||
lai:collection-extensions@0.2.1_1
|
||||
lamhieu:meteorx@2.0.1
|
||||
launch-screen@1.1.1
|
||||
less@2.8.0
|
||||
littledata:synced-cron@1.5.1
|
||||
@@ -72,32 +73,33 @@ localstorage@1.2.0
|
||||
logging@1.1.20
|
||||
matb33:collection-hooks@0.8.4
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.9.2
|
||||
meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
minifier-css@1.4.0
|
||||
minifier-js@2.4.0
|
||||
minifier-css@1.4.2
|
||||
minimongo@1.4.5
|
||||
mobile-experience@1.0.5
|
||||
mobile-status-bar@1.0.14
|
||||
modern-browsers@0.1.2
|
||||
modern-browsers@0.1.4
|
||||
modules@0.13.0
|
||||
modules-runtime@0.10.2
|
||||
momentjs:moment@2.22.2
|
||||
mongo@1.6.0
|
||||
mongo-decimal@0.1.0
|
||||
modules-runtime@0.10.3
|
||||
momentjs:moment@2.24.0
|
||||
mongo@1.6.2
|
||||
mongo-decimal@0.1.1
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
mongo-livedata@1.0.12
|
||||
montiapm:agent@2.35.0
|
||||
nikogosovd:multiple-uihooks@0.1.8
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@3.1.1
|
||||
oauth@1.2.3
|
||||
npm-mongo@3.1.2
|
||||
oauth@1.2.8
|
||||
oauth2@1.2.1
|
||||
observe-sequence@1.0.16
|
||||
ongoworks:speakingurl@9.0.0
|
||||
ordered-dict@1.1.0
|
||||
percolate:migrations@0.9.8
|
||||
promise@0.11.1
|
||||
promise@0.11.2
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.1.0
|
||||
rate-limit@1.0.9
|
||||
@@ -107,9 +109,9 @@ reload@1.2.0
|
||||
retry@1.1.0
|
||||
reywood:iron-router-ga@0.7.1
|
||||
routepolicy@1.1.0
|
||||
seba:minifiers-autoprefixer@1.1.1
|
||||
seba:minifiers-autoprefixer@1.1.2
|
||||
service-configuration@1.0.11
|
||||
session@1.1.8
|
||||
session@1.2.0
|
||||
sha@1.0.9
|
||||
shell-server@0.4.0
|
||||
socket-stream-client@0.2.2
|
||||
@@ -119,7 +121,6 @@ spacebars-compiler@1.1.3
|
||||
splendido:accounts-emails-field@1.2.0
|
||||
splendido:accounts-meld@1.3.1
|
||||
srp@1.0.12
|
||||
standard-minifier-js@2.4.0
|
||||
templates:array@1.0.3
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
@@ -132,8 +133,9 @@ url@1.2.0
|
||||
useraccounts:core@1.14.2
|
||||
useraccounts:iron-routing@1.14.2
|
||||
useraccounts:polymer@1.14.2
|
||||
webapp@1.7.0
|
||||
webapp@1.7.3
|
||||
webapp-hashing@1.0.9
|
||||
wizonesolutions:canonical@0.0.5
|
||||
zimme:collection-behaviours@1.1.3
|
||||
zimme:collection-softremovable@1.0.5
|
||||
zodern:minifier-js@3.0.0
|
||||
zodern:standard-minifier-js@3.0.0
|
||||
|
||||
26
app/Model/Meta/PatreonPosts.js
Normal file
26
app/Model/Meta/PatreonPosts.js
Normal file
@@ -0,0 +1,26 @@
|
||||
PatreonPosts = new Mongo.Collection("patreonPosts");
|
||||
|
||||
Schemas.PatreonPosts = new SimpleSchema({
|
||||
link: {
|
||||
type: String,
|
||||
},
|
||||
dateAdded: {
|
||||
type: Date,
|
||||
autoValue(){
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
PatreonPosts.attachSchema(Schemas.PatreonPosts);
|
||||
|
||||
PatreonPosts.allow({
|
||||
insert: function(userId, doc) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
remove: function(userId, doc) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
});
|
||||
@@ -70,6 +70,45 @@ Schemas.User = new SimpleSchema({
|
||||
index: 1,
|
||||
optional: true,
|
||||
},
|
||||
lastPatreonPostClicked: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
patreon: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
},
|
||||
"patreon.accessToken": {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
"patreon.refreshToken": {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
"patreon.tokenExpiryDate": {
|
||||
type: Date,
|
||||
optional: true,
|
||||
},
|
||||
"patreon.userId": {
|
||||
type: String,
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
"patreon.entitledCents": {
|
||||
type: Number,
|
||||
decimal: false,
|
||||
optional: true,
|
||||
},
|
||||
"patreon.entitledCentsOverride": {
|
||||
type: Number,
|
||||
decimal: false,
|
||||
optional: true,
|
||||
},
|
||||
"patreon.error": {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.attachSchema(Schemas.User);
|
||||
@@ -107,3 +146,11 @@ if (Meteor.isServer) Meteor.methods({
|
||||
Meteor.users.update(this.userId, {$set: {apiKey}});
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
clickPatreonPost(link) {
|
||||
Meteor.users.update(this.userId, {$set: {
|
||||
lastPatreonPostClicked: link
|
||||
}});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,9 +31,17 @@
|
||||
Settings
|
||||
</paper-icon-item>
|
||||
<paper-icon-item id="characterExport">
|
||||
<iron-icon icon="content-copy" item-icon></iron-icon>
|
||||
<iron-icon icon="exit-to-app" item-icon></iron-icon>
|
||||
Export to Improved Initiative
|
||||
</paper-icon-item>
|
||||
<paper-icon-item id="characterCopy">
|
||||
<iron-icon icon="content-copy" item-icon></iron-icon>
|
||||
Make a copy
|
||||
</paper-icon-item>
|
||||
<paper-icon-item id="characterDump">
|
||||
<iron-icon icon="file-download" item-icon></iron-icon>
|
||||
Download a backup
|
||||
</paper-icon-item>
|
||||
</paper-menu>
|
||||
</paper-menu-button>
|
||||
{{else}}
|
||||
|
||||
@@ -234,6 +234,18 @@ Template.characterSheet.events({
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
"click #characterCopy": function(event, instance){
|
||||
Meteor.call("copyCharacter", this._id, (error, char) => {
|
||||
if (error){
|
||||
console.error(error);
|
||||
} else {
|
||||
Router.go(`/character/${char._id}/${char.urlName || "-"}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
"click #characterDump": function(event, instance){
|
||||
saveCharacterDump(this._id);
|
||||
},
|
||||
"click #unshareCharacter": function(event, instance){
|
||||
pushDialogStack({
|
||||
data: this,
|
||||
|
||||
@@ -58,6 +58,13 @@
|
||||
</paper-fab>
|
||||
{{#simpleTooltip class="always"}} New Character {{/simpleTooltip}}
|
||||
</div>
|
||||
<div>
|
||||
<paper-fab icon="file-upload"
|
||||
class="restoreCharacter"
|
||||
mini>
|
||||
</paper-fab>
|
||||
{{#simpleTooltip class="always"}} Restore from backup {{/simpleTooltip}}
|
||||
</div>
|
||||
{{/fabMenu}}
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
@@ -81,4 +81,13 @@ Template.characterList.events({
|
||||
returnElement: instance.find(`.party[data-id='${partyId}']`),
|
||||
});
|
||||
},
|
||||
"click .restoreCharacter": function(event, instance) {
|
||||
pushDialogStack({
|
||||
template: "characterRestoreDialog",
|
||||
element: event.currentTarget,
|
||||
callback(dump){
|
||||
return;
|
||||
},
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<template name="characterRestoreDialog">
|
||||
<div class="fit layout vertical">
|
||||
<app-header-layout has-scrolling-region class="new-character-dialog flex">
|
||||
<app-header fixed effects="waterfall">
|
||||
<app-toolbar>
|
||||
<div main-title>Restore Character</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="form">
|
||||
<p>
|
||||
Restore a character from a backup file, this will create a new copy of
|
||||
the restored character
|
||||
</p>
|
||||
<paper-input class="fileInput" type="file" label="File"></paper-input><br>
|
||||
{{#if error}}
|
||||
<p style="color: red;">
|
||||
{{error}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{#if loading}}
|
||||
<paper-spinner active></paper-spinner>
|
||||
{{/if}}
|
||||
</div>
|
||||
</app-header-layout>
|
||||
<div class="buttons layout horizontal end-justified">
|
||||
<paper-button class="cancelButton">
|
||||
Cancel
|
||||
</paper-button>
|
||||
<paper-button class="addButton" disabled={{invalid}}>
|
||||
Restore
|
||||
</paper-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,66 @@
|
||||
Template.characterRestoreDialog.onCreated(function(){
|
||||
this.dump = {};
|
||||
this.valid = new ReactiveVar(false);
|
||||
this.error = new ReactiveVar(null);
|
||||
this.loading = new ReactiveVar(false);
|
||||
});
|
||||
|
||||
Template.characterRestoreDialog.helpers({
|
||||
invalid(){
|
||||
return !Template.instance().valid.get();
|
||||
},
|
||||
error(){
|
||||
return Template.instance().error.get();
|
||||
},
|
||||
loading(){
|
||||
return Template.instance().loading.get();
|
||||
},
|
||||
});
|
||||
|
||||
const fail = function(instance){
|
||||
instance.valid.set(false);
|
||||
instance.error.set("Failed to convert file into a valid character");
|
||||
instance.dump = undefined;
|
||||
};
|
||||
|
||||
Template.characterRestoreDialog.events({
|
||||
"input .fileInput": function(event, instance){
|
||||
let input = event.currentTarget.$.input;
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(){
|
||||
let dumpString = reader.result;
|
||||
try {
|
||||
let dump = JSON.parse(dumpString);
|
||||
if (dump && dump.character && dump.collections){
|
||||
instance.valid.set(true);
|
||||
instance.error.set(null);
|
||||
instance.dump = dump;
|
||||
} else {
|
||||
fail(instance);
|
||||
}
|
||||
} catch (e) {
|
||||
fail(instance);
|
||||
}
|
||||
};
|
||||
reader.readAsText(input.files[0]);
|
||||
},
|
||||
"click .cancelButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
"click .addButton": function(event, instance){
|
||||
let dump = instance.dump;
|
||||
if (!dump) return;
|
||||
Meteor.call('restoreCharacter', dump, (e, char) => {
|
||||
instance.loading.set(false);
|
||||
if (!char){
|
||||
instance.error.set(e.message)
|
||||
} else {
|
||||
popDialogStack();
|
||||
Router.go("characterSheet", {
|
||||
_id: char._id,
|
||||
urlName: char.urlName || '-',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -21,6 +21,9 @@
|
||||
<a href="/account" style="text-decoration: underline; cursor: pointer; font-size: 16px;">
|
||||
{{profileLink}}
|
||||
</a>
|
||||
<a href="/account" style="text-decoration: underline; cursor: pointer; font-size: 16px; margin-left: 8px;">
|
||||
{{patreonTier}} tier
|
||||
</a>
|
||||
{{else}}
|
||||
<a href="/sign-in" style="text-decoration: underline; cursor: pointer; font-size: 16px;">
|
||||
Sign in
|
||||
@@ -41,6 +44,14 @@
|
||||
Characters
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
{{#if isTier5}}
|
||||
<a href="/library" tabindex="-1">
|
||||
<paper-icon-item id="libary">
|
||||
<iron-icon icon="book" item-icon></iron-icon>
|
||||
Library (beta)
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
{{/if}}
|
||||
<a href="/guide" tabindex="-1">
|
||||
<paper-icon-item id="guide">
|
||||
<iron-icon icon="social:school" item-icon></iron-icon>
|
||||
@@ -51,9 +62,16 @@
|
||||
<iron-icon icon="bug-report" item-icon></iron-icon>
|
||||
Send Feedback
|
||||
</paper-icon-item>
|
||||
<a class="patreon" href="https://www.patreon.com/dicecloud" target="_blank" tabindex="-1">
|
||||
<a class="patreon" href="{{patreonLink}}" target="_blank" tabindex="-1">
|
||||
<paper-icon-item>
|
||||
<iron-icon icon="dicecloud:patreon" item-icon></iron-icon>
|
||||
<iron-icon id="patreon-link-icon" icon="dicecloud:patreon" item-icon></iron-icon>
|
||||
{{#if showPatreonBadge}}
|
||||
<paper-badge
|
||||
icon="av:new-releases"
|
||||
for="patreon-link-icon"
|
||||
label="New post">
|
||||
</paper-badge>
|
||||
{{/if}}
|
||||
Patreon
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
|
||||
@@ -7,6 +7,37 @@ Template.appDrawer.helpers({
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username || user.username || "My Account";
|
||||
},
|
||||
showPatreonBadge: function(){
|
||||
let post = PatreonPosts.findOne({}, {sort: {date: -1}});
|
||||
let user = Meteor.user();
|
||||
if (!post || !user) return false;
|
||||
return post.link !== user.lastPatreonPostClicked;
|
||||
},
|
||||
patreonLink: function(){
|
||||
let post = PatreonPosts.findOne({}, {sort: {date: -1}});
|
||||
return (post && post.link) || 'https://www.patreon.com/dicecloud';
|
||||
},
|
||||
isTier5: function(){
|
||||
let user = Meteor.user();
|
||||
if (!user) return false;
|
||||
patreon = user.patreon;
|
||||
if (!patreon) return false;
|
||||
return patreon.entitledCents >= 500 || patreon.entitledCentsOverride >= 500;
|
||||
},
|
||||
patreonTier: function(){
|
||||
let user = Meteor.user();
|
||||
if (!user) return;
|
||||
patreon = user.patreon;
|
||||
if (!patreon) return "free";
|
||||
let entitledCents = patreon.entitledCents || 0;
|
||||
if (patreon.entitledCentsOverride > entitledCents){
|
||||
return "$" + (patreon.entitledCentsOverride / 100).toFixed(0);
|
||||
} else if (!patreon.entitledCents){
|
||||
return "free";
|
||||
} else {
|
||||
return "$" + (patreon.entitledCents / 100).toFixed(0);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let drawerLayout;
|
||||
@@ -37,6 +68,9 @@ Template.appDrawer.events({
|
||||
closeDrawer(instance);
|
||||
},
|
||||
"click .patreon": function(event, instance){
|
||||
let post = PatreonPosts.findOne({}, {sort: {date: -1}});
|
||||
let link = (post && post.link) || 'https://www.patreon.com/dicecloud';
|
||||
Meteor.call('clickPatreonPost', link);
|
||||
ga("send", "event", "externalLink", "patreon");
|
||||
},
|
||||
"click .github": function(event, instance){
|
||||
|
||||
@@ -62,6 +62,28 @@
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Patreon
|
||||
</td>
|
||||
{{#if patreon.accessToken}}
|
||||
<td>
|
||||
{{tier}} tier
|
||||
</td>
|
||||
<td>
|
||||
<paper-icon-button icon="refresh" class="refreshPatreon">
|
||||
</paper-icon-button>
|
||||
</td>
|
||||
{{else}}
|
||||
<td>
|
||||
<a href="{{patreonLoginUrl}}">
|
||||
<paper-button raised class="connectPatreon">
|
||||
Connect Patreon account
|
||||
</paper-button>
|
||||
</a>
|
||||
</td>
|
||||
{{/if}}
|
||||
</tr>
|
||||
</table>
|
||||
<div style="max-width: 250px">
|
||||
{{> atForm state="signIn"}}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { format as formatUrl } from 'url';
|
||||
|
||||
const CLIENT_ID = Meteor.settings.public.patreon.clientId;
|
||||
|
||||
Template.profile.onCreated(function(){
|
||||
this.showApiKey = new ReactiveVar(false);
|
||||
this.loadingPatreon = new ReactiveVar(false);
|
||||
});
|
||||
|
||||
Template.profile.helpers({
|
||||
@@ -12,6 +17,40 @@ Template.profile.helpers({
|
||||
showApiKey: function(){
|
||||
return Template.instance().showApiKey.get();
|
||||
},
|
||||
patreonLoginUrl: function(){
|
||||
return formatUrl({
|
||||
protocol: 'https',
|
||||
host: 'patreon.com',
|
||||
pathname: '/oauth2/authorize',
|
||||
query: {
|
||||
response_type: 'code',
|
||||
client_id: CLIENT_ID,
|
||||
redirect_uri: Meteor.absoluteUrl() + 'patreon-redirect',
|
||||
state: Meteor.userId(),
|
||||
scope: 'identity',
|
||||
},
|
||||
});
|
||||
},
|
||||
patreon: function(){
|
||||
let user = Meteor.user();
|
||||
return user && user.patreon || {};
|
||||
},
|
||||
tier: function(){
|
||||
let user = Meteor.user();
|
||||
if (!user) return;
|
||||
patreon = user.patreon;
|
||||
if (!patreon) return;
|
||||
let entitledCents = patreon.entitledCents || 0;
|
||||
if (Template.instance().loadingPatreon.get()){
|
||||
return "loading..."
|
||||
} else if (patreon.entitledCentsOverride > entitledCents){
|
||||
return `$ ${(patreon.entitledCentsOverride / 100).toFixed(0)} (overridden)`;
|
||||
} else if (patreon.entitledCents === undefined){
|
||||
return "?";
|
||||
} else {
|
||||
return "$" + (patreon.entitledCents / 100).toFixed(0);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.profile.events({
|
||||
@@ -39,4 +78,10 @@ Template.profile.events({
|
||||
Meteor.call("generateMyApiKey");
|
||||
instance.showApiKey.set(true);
|
||||
},
|
||||
"click .refreshPatreon": function(event, instance){
|
||||
instance.loadingPatreon.set(true);
|
||||
Meteor.call("updateMyPatreonDetails", (error) => {
|
||||
instance.loadingPatreon.set(false);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"polyfill": "/components/webcomponentsjs/webcomponents.min.js",
|
||||
"useShadowDom": true,
|
||||
"imports": [
|
||||
"/components/app-layout/app-layout.html",
|
||||
"/components/app-layout/app-layout.html",
|
||||
"/components/app-layout/app-layout.html",
|
||||
"/components/app-layout/app-layout.html",
|
||||
"/components/app-layout/app-scroll-effects/effects/waterfall.html",
|
||||
"/components/app-layout/app-scroll-effects/effects/parallax-background.html",
|
||||
"/components/app-layout/app-scroll-effects/effects/resize-title.html",
|
||||
|
||||
"/components/iron-collapse/iron-collapse.html",
|
||||
"/components/iron-collapse/iron-collapse.html",
|
||||
"/components/iron-icon/iron-icon.html",
|
||||
"/components/iron-icons/av-icons.html",
|
||||
"/components/iron-icons/editor-icons.html",
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
"/components/neon-animation/neon-animation.html",
|
||||
|
||||
"/components/paper-button/paper-button.html",
|
||||
"/components/paper-button/paper-button.html",
|
||||
"/components/paper-badge/paper-badge.html",
|
||||
"/components/paper-swatch-picker/paper-swatch-picker.html",
|
||||
"/components/paper-dialog/paper-dialog.html",
|
||||
"/components/paper-dropdown-menu/paper-dropdown-menu.html",
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
};
|
||||
99
app/lib/functions/backupRestoreCharacter.js
Normal file
99
app/lib/functions/backupRestoreCharacter.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
let characterCollections = [];
|
||||
Meteor.startup(() => {
|
||||
characterCollections = [
|
||||
Actions,
|
||||
Attacks,
|
||||
Buffs,
|
||||
Classes,
|
||||
Conditions,
|
||||
CustomBuffs,
|
||||
Effects,
|
||||
Experiences,
|
||||
Features,
|
||||
Notes,
|
||||
Proficiencies,
|
||||
SpellLists,
|
||||
Spells,
|
||||
TemporaryHitPoints,
|
||||
Items,
|
||||
Containers,
|
||||
];
|
||||
});
|
||||
|
||||
dumpCharacter = function(charId){
|
||||
let characterDump = {collections: {}};
|
||||
characterDump.character = Characters.findOne(charId);
|
||||
characterCollections.forEach(c => {
|
||||
characterDump.collections[c._name] = c.find({charId}).fetch();
|
||||
});
|
||||
return characterDump;
|
||||
};
|
||||
|
||||
saveCharacterDump = function(charId){
|
||||
let dump = dumpCharacter(charId);
|
||||
let textDump = JSON.stringify(dump, null, 2);
|
||||
let charName = dump.character.name;
|
||||
let blob = new Blob([textDump], {type: "application/json;charset=utf-8"});
|
||||
saveAs(blob, `${charName}.JSON`);
|
||||
};
|
||||
|
||||
giveCharacterDumpNewIds = function(characterDump){
|
||||
// Give the character a new Id
|
||||
const oldCharId = characterDump.character._id;
|
||||
const newCharId = Random.id();
|
||||
characterDump.character._id = newCharId;
|
||||
|
||||
let idMap = {[oldCharId]: newCharId}; // {oldId: newId}
|
||||
|
||||
// Give all documents a new Id, and store the mapping from old to new
|
||||
for (let colName in characterDump.collections){
|
||||
for (let doc of characterDump.collections[colName]){
|
||||
let oldId = doc._id;
|
||||
let newId = Random.id();
|
||||
doc._id = newId;
|
||||
idMap[oldId] = newId;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace all references to old Ids with new ones
|
||||
for (let colName in characterDump.collections){
|
||||
for (let doc of characterDump.collections[colName]){
|
||||
// Replace the character Id with the new one
|
||||
doc.charId = newCharId;
|
||||
// Replace the parent reference id with a new id
|
||||
if (doc.parent && doc.parent.id){
|
||||
let newParentId = idMap[doc.parent.id];
|
||||
if(!newParentId) throw `Can't find the mapping for id ${doc.parent.id}`;
|
||||
doc.parent.id = newParentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
restoreCharacter = function(characterDump){
|
||||
Characters.direct.insert(characterDump.character);
|
||||
for (collectionName in characterDump.collections){
|
||||
let collection = Meteor.Collection.get(collectionName);
|
||||
for (doc of characterDump.collections[collectionName]){
|
||||
// delete problematic keys that shouldn't ever be available on insert
|
||||
delete doc.restoredAt;
|
||||
delete doc.restoredBy;
|
||||
// Insert the doc with no hooks
|
||||
collection.direct.insert(doc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
restoreCharacter(characterDump){
|
||||
characterDump.character.name += " - Restored"
|
||||
characterDump.character.owner = Meteor.userId();
|
||||
characterDump.character.readers = [];
|
||||
characterDump.character.writers = [];
|
||||
giveCharacterDumpNewIds(characterDump);
|
||||
restoreCharacter(characterDump);
|
||||
return characterDump.character
|
||||
},
|
||||
});
|
||||
@@ -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}});
|
||||
@@ -5,12 +12,14 @@ canEditCharacter = function(charId, userId){
|
||||
return (userId === char.owner || _.contains(char.writers, userId));
|
||||
};
|
||||
|
||||
canViewCharacter = function(charId, userId){
|
||||
canViewCharacter = function(char, userId){
|
||||
userId = userId || Meteor.userId();
|
||||
var char = Characters.findOne(
|
||||
charId,
|
||||
{fields: {owner: 1, writers: 1, readers: 1, "settings.viewPermission": 1}}
|
||||
);
|
||||
if (typeof char !== 'object'){
|
||||
char = Characters.findOne(
|
||||
char,
|
||||
{fields: {owner: 1, writers: 1, readers: 1, "settings.viewPermission": 1}}
|
||||
);
|
||||
}
|
||||
if (!char) return true;
|
||||
return userId === char.owner ||
|
||||
char.settings.viewPermission === "public" ||
|
||||
|
||||
26
app/lib/methods/characterCopyPaste.js
Normal file
26
app/lib/methods/characterCopyPaste.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Uses '/lib/functions/backupRestoreCharacter.js' to do most the work
|
||||
|
||||
Meteor.methods({
|
||||
copyCharacter: function(charId) {
|
||||
const userId = Meteor.userId();
|
||||
let character = Characters.findOne(charId);
|
||||
|
||||
// Need at least view level permission to make a copy for yourself
|
||||
if (!canViewCharacter(character, userId)) return;
|
||||
|
||||
let characterDump = dumpCharacter(charId);
|
||||
giveCharacterDumpNewIds(characterDump);
|
||||
|
||||
// Remove all readers and writers, make this user the new owner
|
||||
characterDump.character.readers = [];
|
||||
characterDump.character.writers = [];
|
||||
characterDump.character.owner = userId;
|
||||
|
||||
// Rename the character so it's obviously a copy
|
||||
characterDump.character.name += " - Copy";
|
||||
|
||||
// Write the character back to the database
|
||||
restoreCharacter(characterDump);
|
||||
return characterDump.character;
|
||||
},
|
||||
});
|
||||
552
app/package-lock.json
generated
552
app/package-lock.json
generated
@@ -26,26 +26,19 @@
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"ajv": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
|
||||
"version": "6.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
|
||||
"integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
|
||||
"requires": {
|
||||
"co": "^4.6.0",
|
||||
"fast-deep-equal": "^1.0.0",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||
}
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
|
||||
},
|
||||
"aproba": {
|
||||
@@ -53,16 +46,34 @@
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||
"requires": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
|
||||
},
|
||||
"aws4": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
||||
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-1.0.3.tgz",
|
||||
@@ -72,17 +83,26 @@
|
||||
"node-pre-gyp": "0.6.36"
|
||||
}
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||
"requires": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
|
||||
"requires": {
|
||||
"inherits": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"bower": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/bower/-/bower-1.8.4.tgz",
|
||||
"integrity": "sha1-54dqB23rgTf30GUl3F6MZtuC8oo="
|
||||
"version": "1.8.8",
|
||||
"resolved": "https://registry.npmjs.org/bower/-/bower-1.8.8.tgz",
|
||||
"integrity": "sha512-1SrJnXnkP9soITHptSO+ahx3QKp3cVzn8poI6ujqc5SeOkg5iqM1pK9H+DSc2OQ8SnO0jC/NG4Ur/UIwy7574A=="
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
@@ -107,6 +127,11 @@
|
||||
"window-or-global": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||
},
|
||||
"cliui": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
@@ -131,14 +156,27 @@
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.5.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
|
||||
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||
@@ -149,6 +187,14 @@
|
||||
"which": "^1.2.9"
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -162,11 +208,33 @@
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||
},
|
||||
"dijkstrajs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz",
|
||||
"integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs="
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||
"requires": {
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"requires": {
|
||||
"iconv-lite": "~0.4.13"
|
||||
}
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
@@ -189,15 +257,20 @@
|
||||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
|
||||
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
|
||||
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.0.0",
|
||||
@@ -209,6 +282,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fibers/-/fibers-2.0.2.tgz",
|
||||
"integrity": "sha512-HfVRxhYG7C8Jl9FqtrlElMR2z/8YiLQVDKf67MLY25Ic+ILx3ecmklfT1v3u+7P5/4vEFjuxaAFXhr2/Afwk5g=="
|
||||
},
|
||||
"file-saver": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.1.tgz",
|
||||
"integrity": "sha512-dCB3K7/BvAcUmtmh1DzFdv0eXSVJ9IAFt1mw3XZfAexodNRoE29l3xB2EX4wH2q8m/UTzwzEPq/ArYk98kUkBQ=="
|
||||
},
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
@@ -217,36 +295,26 @@
|
||||
"locate-path": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
|
||||
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "1.0.6",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
|
||||
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-urlencoded": {
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/form-urlencoded/-/form-urlencoded-2.0.9.tgz",
|
||||
"integrity": "sha512-fWUzNiOnYa126vFAT6TFXd1mhJrvD8IqmQ9ilZPjkLYQfaRreBr5fIUoOpPlWtqaAG64nzoE7u5zSetifab9IA=="
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
|
||||
@@ -257,9 +325,17 @@
|
||||
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||
},
|
||||
"har-schema": {
|
||||
@@ -268,11 +344,11 @@
|
||||
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
|
||||
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||
"requires": {
|
||||
"ajv": "^5.1.0",
|
||||
"ajv": "^6.5.5",
|
||||
"har-schema": "^2.0.0"
|
||||
}
|
||||
},
|
||||
@@ -289,78 +365,19 @@
|
||||
"assert-plus": "^1.0.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"asn1": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
|
||||
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
|
||||
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"jsbn": "~0.1.0"
|
||||
}
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
|
||||
"optional": true
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz",
|
||||
"integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=",
|
||||
"requires": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"tweetnacl": "~0.14.0"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"invert-kv": {
|
||||
@@ -383,17 +400,30 @@
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"requires": {
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz",
|
||||
@@ -404,10 +434,49 @@
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
||||
"requires": {
|
||||
"node-fetch": "^1.0.1",
|
||||
"whatwg-fetch": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
|
||||
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||
},
|
||||
"jsonapi-datastore": {
|
||||
"version": "0.4.0-beta",
|
||||
"resolved": "https://registry.npmjs.org/jsonapi-datastore/-/jsonapi-datastore-0.4.0-beta.tgz",
|
||||
"integrity": "sha1-tJn86STUXivDxheGgVIAY+I2HxA="
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
@@ -418,13 +487,6 @@
|
||||
"extsprintf": "1.3.0",
|
||||
"json-schema": "0.2.3",
|
||||
"verror": "1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"json-schema": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
||||
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
||||
}
|
||||
}
|
||||
},
|
||||
"lcid": {
|
||||
@@ -1154,16 +1216,16 @@
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
|
||||
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
|
||||
"version": "1.38.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
|
||||
"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.18",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
|
||||
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
|
||||
"version": "2.1.22",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
|
||||
"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
|
||||
"requires": {
|
||||
"mime-db": "~1.33.0"
|
||||
"mime-db": "~1.38.0"
|
||||
}
|
||||
},
|
||||
"mimic-fn": {
|
||||
@@ -1181,6 +1243,15 @@
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
|
||||
"integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||
"requires": {
|
||||
"encoding": "^0.1.11",
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.6.36",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz",
|
||||
@@ -1434,9 +1505,14 @@
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
@@ -1518,6 +1594,17 @@
|
||||
"pify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"patreon": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/patreon/-/patreon-0.4.1.tgz",
|
||||
"integrity": "sha512-aLhjx4rg2BArTq0Kg61MrM4dkJnTQ9kPN8F6a2IlQoYVEtIH7kUK/dprClTx+QYQKlXMfKksN9NCux1YarQJsQ==",
|
||||
"requires": {
|
||||
"form-urlencoded": "^2.0.4",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"jsonapi-datastore": "^0.4.0-beta"
|
||||
}
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
@@ -1538,6 +1625,16 @@
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.1.31",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
|
||||
"integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
},
|
||||
"qrcode": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.3.0.tgz",
|
||||
@@ -1578,6 +1675,7 @@
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"strip-json-comments": {
|
||||
@@ -1612,103 +1710,30 @@
|
||||
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
|
||||
},
|
||||
"request": {
|
||||
"version": "2.87.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
|
||||
"integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
|
||||
"version": "2.88.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.6.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.5",
|
||||
"extend": "~3.0.1",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.1",
|
||||
"har-validator": "~5.0.3",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.0",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.17",
|
||||
"oauth-sign": "~0.8.2",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.1",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"tough-cookie": "~2.3.3",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.4.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws4": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz",
|
||||
"integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w=="
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
|
||||
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
||||
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
|
||||
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
|
||||
}
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
@@ -1808,6 +1833,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
@@ -1815,7 +1850,7 @@
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
||||
},
|
||||
"shebang-command": {
|
||||
@@ -1833,7 +1868,7 @@
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"source-map": {
|
||||
@@ -1878,6 +1913,22 @@
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz",
|
||||
"integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w=="
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
|
||||
"requires": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
@@ -1909,7 +1960,7 @@
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
@@ -2089,10 +2140,11 @@
|
||||
}
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
||||
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
||||
"requires": {
|
||||
"psl": "^1.1.24",
|
||||
"punycode": "^1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -2103,11 +2155,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
|
||||
"integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||
},
|
||||
"validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
@@ -2125,13 +2203,6 @@
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
}
|
||||
}
|
||||
},
|
||||
"webcomponents.js": {
|
||||
@@ -2139,6 +2210,11 @@
|
||||
"resolved": "https://registry.npmjs.org/webcomponents.js/-/webcomponents.js-0.7.24.tgz",
|
||||
"integrity": "sha1-IRb7+hRo7EFqe+/aozPh0Rj2nAQ="
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
|
||||
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
@@ -2159,7 +2235,7 @@
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"requires": {
|
||||
"string-width": "^1.0.1",
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"@polymer/polymer": "^1.2.5-npm-test.2",
|
||||
"bcrypt": "^1.0.3",
|
||||
"bower": "^1.7.9",
|
||||
"bower": "^1.8.8",
|
||||
"core-js": "^2.5.7",
|
||||
"fibers": "^2.0.2",
|
||||
"file-saver": "^2.0.1",
|
||||
"meteor-node-stubs": "^0.3.3",
|
||||
"patreon": "^0.4.1",
|
||||
"qrcode": "^1.3.0",
|
||||
"request": "^2.88.0",
|
||||
"source-map-support": "^0.5.9",
|
||||
"underscore": "^1.9.1"
|
||||
}
|
||||
|
||||
262
app/server/patreon/patreon.js
Normal file
262
app/server/patreon/patreon.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import request from 'request';
|
||||
|
||||
if (
|
||||
Meteor.settings &&
|
||||
Meteor.settings.public &&
|
||||
Meteor.settings.public.patreon
|
||||
) {
|
||||
const CLIENT_ID = Meteor.settings.public.patreon.clientId;
|
||||
const CLIENT_SECRET = Meteor.settings.patreon.clientSecret;
|
||||
const CREATOR_ACCESS_TOKEN = Meteor.settings.patreon.creatorAccessToken;
|
||||
const CAMPAIGN_ID = Meteor.settings.public.patreon.campaignId;
|
||||
|
||||
// Handle redirects from patreon
|
||||
Router.map(function () {
|
||||
this.route("patreon-redirect", {
|
||||
path: "/patreon-redirect",
|
||||
where: "server",
|
||||
action: function () {
|
||||
let route = this;
|
||||
let userId = route.params.query.state;
|
||||
let singleUseCode = route.params.query.code;
|
||||
requestToken(singleUseCode, Meteor.bindEnvironment((error, response, body) => {
|
||||
// Should return an access token, valid for 1 month, which needs to be
|
||||
// stored and used to make requests on behalf of the user
|
||||
if (error){
|
||||
writePatreonError(userId, error);
|
||||
return;
|
||||
}
|
||||
let token;
|
||||
try {
|
||||
token = JSON.parse(body);
|
||||
writePatreonToken(userId, token);
|
||||
} catch(error) {
|
||||
writePatreonError(userId, error);
|
||||
return;
|
||||
}
|
||||
updateIdentity(token.access_token, userId);
|
||||
}));
|
||||
route.response.writeHead(302, {
|
||||
'Location': Meteor.absoluteUrl() + "account",
|
||||
});
|
||||
route.response.end();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const requestToken = function(singleUseCode, callback){
|
||||
request({
|
||||
method: "POST",
|
||||
uri: "https://www.patreon.com/api/oauth2/token",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
qs: {
|
||||
code: singleUseCode,
|
||||
grant_type: "authorization_code",
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
redirect_uri: Meteor.absoluteUrl() + 'patreon-redirect',
|
||||
},
|
||||
}, callback);
|
||||
}
|
||||
|
||||
const getIdentity = function(accessToken, callback){
|
||||
request({
|
||||
uri: "https://www.patreon.com/api/oauth2/v2/identity",
|
||||
headers:{
|
||||
Authorization: "Bearer " + accessToken,
|
||||
},
|
||||
qs: {
|
||||
"include": "memberships",
|
||||
"fields[member]": "currently_entitled_amount_cents",
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
// Should return a new access token for the user
|
||||
// callback is called with (error, response, body)
|
||||
const refreshAccessToken = Meteor.wrapAsync(function(refreshToken, userId, callback){
|
||||
request({
|
||||
method: "POST",
|
||||
uri: "https://www.patreon.com/api/oauth2/token",
|
||||
qs: {
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
}
|
||||
}, Meteor.bindEnvironment((error, response, body) => {
|
||||
// Should return an access token, valid for 1 month, which needs to be
|
||||
// stored and used to make requests on behalf of the user
|
||||
if (error){
|
||||
callback(error)
|
||||
return;
|
||||
}
|
||||
let token;
|
||||
try {
|
||||
token = JSON.parse(body);
|
||||
writePatreonToken(userId, token);
|
||||
callback(undefined, token.access_token);
|
||||
} catch(error) {
|
||||
callback(error);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
const updateIdentity = Meteor.wrapAsync(function(accessToken, userId, callback){
|
||||
getIdentity(accessToken, Meteor.bindEnvironment((error, response, body) => {
|
||||
if (error){
|
||||
writePatreonError(userId, error);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let identity = JSON.parse(body);
|
||||
let membership = identity.included[0];
|
||||
let entitledAmount = membership && membership.attributes
|
||||
.currently_entitled_amount_cents || 0;
|
||||
let patreonUserId = identity.data.id;
|
||||
writeEntitledCentsAndId(userId, entitledAmount, patreonUserId);
|
||||
if (callback) callback();
|
||||
} catch(error) {
|
||||
writePatreonError(userId, error);
|
||||
if(callback) callback(error);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
updateMyPatreonDetails(){
|
||||
const userId = this.userId;
|
||||
if (!userId) throw new Meteor.Error("not-logged-in", "You must be logged in to update Patreon details");
|
||||
const user = Meteor.users.findOne(userId, {fields: {patreon: 1}});
|
||||
Meteor.users.update(userId, {$unset: {"patreon.entitledCents": 1}});
|
||||
if (!user.patreon || !user.patreon.accessToken){
|
||||
throw new Meteor.Error("no-patreon-access", "Patreon access token not found for this user");
|
||||
}
|
||||
let accessToken = user.patreon.accessToken;
|
||||
if (user.patreon.tokenExpiryDate < new Date()){
|
||||
// Token expired, refresh it before continuing
|
||||
accessToken = refreshAccessToken(user.patreon.refreshToken, userId);
|
||||
}
|
||||
updateIdentity(accessToken, userId);
|
||||
},
|
||||
});
|
||||
|
||||
const writePatreonToken = function(userId, {
|
||||
access_token, refresh_token, expires_in
|
||||
}){
|
||||
// The expiry date is now plus `expires_in` seconds
|
||||
let expiryDate = new Date();
|
||||
expiryDate.setSeconds(expiryDate.getSeconds() + expires_in);
|
||||
// Expire a day early so we don't accidentally miss it
|
||||
expiryDate.setDate(expiryDate.getDate() - 1);
|
||||
|
||||
// Write
|
||||
Meteor.users.update(userId, {
|
||||
$set: {
|
||||
"patreon.accessToken": access_token,
|
||||
"patreon.refreshToken": refresh_token,
|
||||
"patreon.tokenExpiryDate": expiryDate,
|
||||
},
|
||||
$unset: {
|
||||
"patreon.error": 1,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const writeEntitledCentsAndId = function(userId, amount, patreonUserId){
|
||||
Meteor.users.update(userId, {
|
||||
$set: {
|
||||
"patreon.entitledCents": amount,
|
||||
"patreon.userId": patreonUserId,
|
||||
},
|
||||
$unset: {
|
||||
"patreon.error": 1,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const writePatreonError = function(userId, error){
|
||||
console.error({patreonError: error});
|
||||
Meteor.users.update(userId, {
|
||||
$set: {
|
||||
"patreon.error": error.toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const requestMembers = Meteor.wrapAsync(function(cursor, members, callback){
|
||||
request({
|
||||
uri: `https://www.patreon.com/api/oauth2/v2/campaigns/${CAMPAIGN_ID}/members`,
|
||||
headers:{
|
||||
Authorization: "Bearer " + CREATOR_ACCESS_TOKEN,
|
||||
},
|
||||
qs: {
|
||||
"include": "user",
|
||||
"fields[member]": "currently_entitled_amount_cents",
|
||||
"page[cursor]": cursor,
|
||||
}
|
||||
}, (error, reponse, body) => {
|
||||
if (error){
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
let json = JSON.parse(body);
|
||||
if (json.errors) {
|
||||
callback(json.errors);
|
||||
return;
|
||||
}
|
||||
let newMembers = json.data.map(member => ({
|
||||
id: member.relationships.user.data.id,
|
||||
entitledCents: member.attributes.currently_entitled_amount_cents,
|
||||
}));
|
||||
members.push(...newMembers);
|
||||
let next = json.meta.pagination.cursors && json.meta.pagination.cursors.next;
|
||||
if (next){
|
||||
callback(undefined, next);
|
||||
} else {
|
||||
callback(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const updatePatreonMembersEntitledCents = function(){
|
||||
let next = "";
|
||||
let members = [];
|
||||
do {
|
||||
next = requestMembers(next, members);
|
||||
} while (next)
|
||||
members.forEach(({id, entitledCents}) => {
|
||||
Meteor.users.update({
|
||||
"patreon.userId": id
|
||||
}, {$set: {
|
||||
"patreon.entitledCents":entitledCents,
|
||||
}});
|
||||
});
|
||||
return members;
|
||||
}
|
||||
|
||||
// Method to run a manual update
|
||||
Meteor.methods({
|
||||
updatePatreonMembersEntitledCents(){
|
||||
const user = Meteor.users.findOne(this.userId);
|
||||
if (!user || !_.contains(user.roles, "admin")) throw new Meteor.Error(
|
||||
"permission-error", "You need to be logged in as an admin to run this method"
|
||||
);
|
||||
return updatePatreonMembersEntitledCents();
|
||||
},
|
||||
});
|
||||
|
||||
// Cron job to run the update automatically
|
||||
Meteor.startup(() => {
|
||||
SyncedCron.add({
|
||||
name: "updatePatreonMembersEntitledCents",
|
||||
schedule: function(parser) {
|
||||
return parser.text('every 4 hours');
|
||||
},
|
||||
job: updatePatreonMembersEntitledCents,
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
Meteor.publish("user", function(){
|
||||
return Meteor.users.find(this.userId, {fields: {
|
||||
roles: 1,
|
||||
username: 1,
|
||||
profile: 1,
|
||||
apiKey: 1,
|
||||
librarySubscriptions: 1,
|
||||
}});
|
||||
return [
|
||||
Meteor.users.find(this.userId, {fields: {
|
||||
roles: 1,
|
||||
username: 1,
|
||||
profile: 1,
|
||||
apiKey: 1,
|
||||
librarySubscriptions: 1,
|
||||
lastPatreonPostClicked: 1,
|
||||
patreon: 1,
|
||||
}}),
|
||||
PatreonPosts.find({},{sort: {dateAdded: -1}, limit: 1})
|
||||
];
|
||||
});
|
||||
|
||||
@@ -2194,9 +2194,9 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"castingTime": "bonus action",
|
||||
"castingTime": "action",
|
||||
"description": "Choose a manufactured metal object, such as a metal weapon or a suit of heavy or medium metal armor, that you can see within range. You cause the object to glow red-hot. Any creature in physical contact with the object takes 2d8 fire damage when you cast the spell. Until the spell ends, you can use a bonus action on each of your subsequent turns to cause this damage again.\n\nIf a creature is holding or wearing the object and takes the damage from it, the creature must succeed on a DC {DC} Constitution saving throw or drop the object if it can. If it doesn’t drop the object, it has disadvantage on attack rolls and ability checks until the start of your next turn.\n\n***At Higher Levels.*** When you cast this spell using a spell slot of 3rd level or higher, the damage increases by 1d8 for each slot level above 2nd.",
|
||||
"duration": "Instantaneous",
|
||||
"duration": "Concentration, up to 1 minute",
|
||||
"level": 2,
|
||||
"range": "60 feet",
|
||||
"school": "Transmutation",
|
||||
@@ -2204,8 +2204,9 @@
|
||||
"name": "Heat Metal",
|
||||
"components": {
|
||||
"verbal": true,
|
||||
"somatic": false,
|
||||
"concentration": false
|
||||
"somatic": true,
|
||||
"concentration": true,
|
||||
"material": "a piece of iron and a flame"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// This all gets run in the console by an admin.
|
||||
// Probably a good idea to reset the server after running big updates
|
||||
// Only do if the library doesn't exist yet
|
||||
|
||||
// First Setup
|
||||
// -----------
|
||||
|
||||
// Add the SRD library with the correct static ID:
|
||||
id = Libraries.insert({
|
||||
_id: "SRDLibraryGA3XWsd",
|
||||
owner: Meteor.userId(),
|
||||
@@ -8,19 +11,23 @@ id = Libraries.insert({
|
||||
});
|
||||
|
||||
// First copy-paste the JSON into your console like `items = <pasted JSON>`
|
||||
// First import, don't do this if the library is already populated
|
||||
_.each(items, (item) => {
|
||||
item.settings = {category: }; // "adventuringGear", "armor", "weapons", "tools"
|
||||
// replace "adventuringGear" with appropriate category: "armor", "weapons", "tools"
|
||||
// if needed
|
||||
item.settings = {category: "adventuringGear"};
|
||||
item.library = "SRDLibraryGA3XWsd"
|
||||
LibraryItems.insert(item)
|
||||
});
|
||||
|
||||
// First copy-paste the JSON into your console like `spells = <pasted JSON>`
|
||||
_.each(spells, (spell) => {
|
||||
spell.library = "SRDLibraryGA3XWsd"
|
||||
LibrarySpells.insert(spell)
|
||||
});
|
||||
|
||||
// Update the library using names as keys
|
||||
// Updating the Libary
|
||||
// -------------------
|
||||
|
||||
// Make sure you're subscribed to all item categories
|
||||
handles = _.map(["weapons", "armor", "adventuringGear", "tools"],
|
||||
category => Meteor.subscribe("standardLibraryItems", category)
|
||||
|
||||
Reference in New Issue
Block a user