Removed all patreon based restrictions

This commit is contained in:
Stefan Zermatten
2021-07-17 14:10:06 +02:00
parent 03b7f1037e
commit c6b633613c
14 changed files with 2087 additions and 659 deletions

View File

@@ -74,41 +74,6 @@ Schemas.User = new SimpleSchema({
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);

View File

@@ -1,7 +0,0 @@
Template.registerHelper("isTier5", function(){
let user = Meteor.user();
if (!user) return false;
patreon = user.patreon;
if (!patreon) return false;
return patreon.entitledCents >= 300 || patreon.entitledCentsOverride >= 300;
});

View File

@@ -1,24 +0,0 @@
import { format as formatUrl } from 'url';
const CLIENT_ID = Meteor.settings &&
Meteor.settings.public.patreon &&
Meteor.settings.public.patreon.clientId;
Template.registerHelper("patreonLoginUrl", function() {
if (!CLIENT_ID) {
console.warn('Could not find Meteor.settings.public.patreon.clientId to make patreon link')
return;
}
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',
},
});
});

View File

@@ -34,9 +34,6 @@
<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
@@ -60,7 +57,7 @@
<a href="/library" tabindex="-1">
<paper-icon-item id="libary">
<iron-icon icon="book" item-icon></iron-icon>
Libraries (beta)
Item Libraries
</paper-icon-item>
</a>
<a href="/guide" tabindex="-1">

View File

@@ -27,20 +27,6 @@ Template.appDrawer.helpers({
let post = PatreonPosts.findOne({}, {sort: {date: -1}});
return (post && post.link) || 'https://www.patreon.com/dicecloud';
},
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;

View File

@@ -10,26 +10,22 @@
</div>
</app-toolbar>
</app-header>
{{#if isTier5}}
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
{{#each library in libraries}}
<a href="/library/{{library._id}}" tabindex="-1">
<paper-item class="library" data-id="{{library._id}}">
<paper-item-body>
<div>{{library.name}}</div>
</paper-item-body>
</paper-item>
</a>
{{/each}}
</paper-material>
</div>
<div class="floatyButton">
<paper-fab id="addLibrary" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library{{/simpleTooltip}}
</div>
{{else}}
{{> patronsOnly }}
{{/if}}
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
{{#each library in libraries}}
<a href="/library/{{library._id}}" tabindex="-1">
<paper-item class="library" data-id="{{library._id}}">
<paper-item-body>
<div>{{library.name}}</div>
</paper-item-body>
</paper-item>
</a>
{{/each}}
</paper-material>
</div>
<div class="floatyButton">
<paper-fab id="addLibrary" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library{{/simpleTooltip}}
</div>
</div>
</template>

View File

@@ -8,49 +8,43 @@
<div class="flex layout horizontal center" style="height: 40px; margin-left: 8px;">
{{name}}
</div>
{{#if isTier5}}{{#if canUserEdit}}
{{#if canUserEdit}}
<paper-icon-button icon="settings" id="edit"></paper-icon-button>
{{/if}}{{/if}}
{{/if}}
</div>
<div bottom-item class="layout horizontal center">
<paper-input label="Search" class="search-input">
<iron-icon icon="search" prefix></iron-icon>
</paper-input>
<div class="flex"></div>
{{#if canUserSubscribe}}
<paper-button style="color: rgba(255,255,255,0.87);" id="subscribe">
<iron-icon icon="add-circle"></iron-icon>
Subscribe
</paper-button>
{{else if canUserUnsubscribe}}
<paper-button style="color: rgba(255,255,255,0.87);" id="unsubscribe">
<iron-icon icon="remove-circle"></iron-icon>
Unsubscribe
</paper-button>
{{/if}}
</div>
{{#if isTier5}}
<div bottom-item class="layout horizontal center">
<paper-input label="Search" class="search-input">
<iron-icon icon="search" prefix></iron-icon>
</paper-input>
<div class="flex"></div>
{{#if canUserSubscribe}}
<paper-button style="color: rgba(255,255,255,0.87);" id="subscribe">
<iron-icon icon="add-circle"></iron-icon>
Subscribe
</paper-button>
{{else if canUserUnsubscribe}}
<paper-button style="color: rgba(255,255,255,0.87);" id="unsubscribe">
<iron-icon icon="remove-circle"></iron-icon>
Unsubscribe
</paper-button>
{{/if}}
</div>
{{/if}}
</app-toolbar>
</app-header>
{{#if isTier5}}
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
{{#each items}}
<paper-item data-id={{_id}} class="item">
<paper-item-body>
<div>{{displayName}}</div>
</paper-item-body>
</paper-item>
{{/each}}
</paper-material>
</div>
<div class="floatyButton">
<paper-fab id="addLibraryItem" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library Item{{/simpleTooltip}}
</div>
{{else}}
{{> patronsOnly }}
{{/if}}
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
{{#each items}}
<paper-item data-id={{_id}} class="item">
<paper-item-body>
<div>{{displayName}}</div>
</paper-item-body>
</paper-item>
{{/each}}
</paper-material>
</div>
<div class="floatyButton">
<paper-fab id="addLibraryItem" icon="add"></paper-fab>
{{#simpleTooltip}}Add Library Item{{/simpleTooltip}}
</div>
</div>
</template>

View File

@@ -1,27 +0,0 @@
<template name="patronsOnly">
<div class="flex layout vertical center" style="position: relative; padding: 0 16px;">
<paper-material class="card" style="padding: 32px; max-width: 800px; width: 100%;">
<h3>
This beta feature is available to Patreon Insiders who pledge $5 or more
</h3>
<div class="layout vertical center">
<a href="https://www.patreon.com/join/dicecloud/checkout?rid=3002853">
<paper-button raised> Become a Patron </paper-button>
</a>
<a href="{{patreonLoginUrl}}">
<paper-button class="connectPatreon" style="color: #d13b2e; margin-top: 12px;">
Connect Patreon account
</paper-button>
</a>
</div>
<p style="margin-top: 32px;">
With the Item Libraries beta you can create collections of items to use
across your characters, and share them with other players.
</p>
<p>
You can also subscribe to existing community libraries of items, saving
time and effort manually entering item details.
</p>
</paper-material>
</div>
</template>

View File

@@ -62,28 +62,6 @@
{{/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"}}

View File

@@ -1,6 +1,5 @@
Template.profile.onCreated(function(){
this.showApiKey = new ReactiveVar(false);
this.loadingPatreon = new ReactiveVar(false);
});
Template.profile.helpers({
@@ -13,26 +12,6 @@ Template.profile.helpers({
showApiKey: function(){
return Template.instance().showApiKey.get();
},
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({
@@ -60,10 +39,4 @@ 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);
});
},
});

2200
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,6 @@
"core-js": "^2.6.11",
"file-saver": "^2.0.2",
"meteor-node-stubs": "^0.3.3",
"patreon": "^0.4.1",
"qrcode": "^1.4.4",
"request": "^2.88.2",
"source-map-support": "^0.5.19",

View File

@@ -1,271 +0,0 @@
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.currently_entitled_tiers',
'fields[tier]': 'amount_cents,title',
}
}, 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 entitledAmount = 0;
if (identity && identity.included){
identity.included.forEach(doc => {
if (
doc.type === 'tier' &&
doc.attributes &&
doc.attributes.amount_cents > entitledAmount
){
entitledAmount = doc.attributes.amount_cents;
}
});
}
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,
});
})
}

View File

@@ -7,7 +7,6 @@ Meteor.publish("user", function(){
apiKey: 1,
librarySubscriptions: 1,
lastPatreonPostClicked: 1,
patreon: 1,
}}),
PatreonPosts.find({},{sort: {dateAdded: -1}, limit: 1})
];