From 5ce1b6aff8c6a514ed3121f14b691b62cdcffc7d Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 3 Apr 2019 10:16:24 +0200 Subject: [PATCH] closes #210 closes #211 --- README.md | 34 ++- app/.meteor/packages | 5 +- app/.meteor/release | 2 +- app/.meteor/versions | 47 ++-- app/package-lock.json | 68 +---- app/package.json | 2 +- app/server/patreon/patreon.js | 494 +++++++++++++++++----------------- dataSources/srd/srdimport.js | 17 +- 8 files changed, 320 insertions(+), 349 deletions(-) diff --git a/README.md b/README.md index 6c81e7ae..6616df90 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ changes on the fly. Let's look at a hypothetical example. Getting started --------------- -Running DiceCloud locally, either to host it yourself away from an internet +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. @@ -45,18 +45,29 @@ You'll need to have installed: - [git](https://www.atlassian.com/git/tutorials/install-git) - [Meteor](https://www.meteor.com/install) -- [Bower](https://bower.io/) -Then, it's just a matter of cloning this repository into a folder, installing the bower dependencies and running -`meteor` in the app directory. +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` -You should see this: +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. @@ -69,3 +80,14 @@ You should see this: 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. diff --git a/app/.meteor/packages b/app/.meteor/packages index ba4fce08..815922ae 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -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,12 +36,11 @@ ejson@1.1.0 spacebars check@1.3.1 useraccounts:iron-routing -wizonesolutions:canonical 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 diff --git a/app/.meteor/release b/app/.meteor/release index e76dedee..91e05fc1 100644 --- a/app/.meteor/release +++ b/app/.meteor/release @@ -1 +1 @@ -METEOR@1.8 +METEOR@1.8.0.2 diff --git a/app/.meteor/versions b/app/.meteor/versions index f8a3d460..73460e95 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -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 @@ -73,33 +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-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.34.3 +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 @@ -109,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 @@ -133,9 +133,8 @@ 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 diff --git a/app/package-lock.json b/app/package-lock.json index 486bfe7c..b6dc3891 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -100,9 +100,9 @@ } }, "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", @@ -1126,37 +1126,6 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "stream-http": { @@ -1169,37 +1138,6 @@ "readable-stream": "^2.3.3", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "string_decoder": { diff --git a/app/package.json b/app/package.json index bfc69dbf..559c5aef 100644 --- a/app/package.json +++ b/app/package.json @@ -14,7 +14,7 @@ "@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", diff --git a/app/server/patreon/patreon.js b/app/server/patreon/patreon.js index 71d77ad7..27e98522 100644 --- a/app/server/patreon/patreon.js +++ b/app/server/patreon/patreon.js @@ -1,256 +1,262 @@ import request from 'request'; -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; +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(); - }, + // 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 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); -}; + 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, + // 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); + } })); - 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, - }}); + 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); + } + })); }); - return members; + + 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, + }); + }) } - -// 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, - }); -}) diff --git a/dataSources/srd/srdimport.js b/dataSources/srd/srdimport.js index a788b8be..8c5fdb48 100644 --- a/dataSources/srd/srdimport.js +++ b/dataSources/srd/srdimport.js @@ -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 = ` -// 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 = ` _.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)