Initial commit

This commit is contained in:
Alex Thomassen 2022-09-11 21:08:59 +00:00
commit 82c515b2e9
Signed by: Alex
GPG Key ID: 10BD786B5F6FF5DE
5 changed files with 192 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# http://editorconfig.org
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 4
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

2
worker/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
wrangler.toml

146
worker/index.js Normal file
View File

@ -0,0 +1,146 @@
async function hashString(input)
{
const data = new TextEncoder().encode(input);
const hash = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
}
/**
* Refreshes Patreon OAuth token
*
* @param {Object} token
* @returns {Promise<Object>}
*/
async function refreshToken(token)
{
const url = new URL('https://www.patreon.com/api/oauth2/token');
url.searchParams.set('grant_type', 'refresh_token');
url.searchParams.set('refresh_token', token.refresh_token);
url.searchParams.set('client_id', PATREON_CLIENT_ID);
url.searchParams.set('client_secret', PATREON_CLIENT_SECRET);
const response = await fetch(url, {
method: 'POST',
});
return await response.json();
}
/**
* Returns the current Patreon OAuth access token. Will trigger `refreshToken()` if necessary.
*
* @returns {Promise<string>}
*/
async function getToken()
{
let token = await PATREON_CONFIG.get('credentials', {
type: 'json',
});
if (token.expires_at && token.expires_at > Date.now()) {
return token.access_token;
}
token = await refreshToken(token);
token.expires_at = Date.now() + (token.expires_in * 1000);
await PATREON_CONFIG.put('credentials', JSON.stringify(token));
return token.access_token;
}
/**
* Fetches data from the Patreon API.
* Will cache responses for up to 15 minutes, based on final URL (with GET parameters).
*
* @param {String} path
* @param {Object} params
* @returns {Promise<Object>}
*/
async function fetchApi(path, params)
{
if (path[0] !== '/') {
path = '/' + path;
}
const url = new URL(`https://www.patreon.com/api/oauth2/v2${path}`);
for (const key in params) {
url.searchParams.set(key, params[key]);
}
const cacheKey = `CACHE_${await hashString(url.toString())}`;
const cache = await PATREON_CONFIG.get(cacheKey, {
type: 'json',
});
if (cache) {
return cache;
}
const token = await getToken();
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
const data = await response.json();
await PATREON_CONFIG.put(cacheKey, JSON.stringify(data), {
// Cache for 15 minutes
expirationTtl: 15 * 60,
});
return data;
}
/**
* Get campaign goals
*
* @returns {Promise<Object>}
*/
async function getGoals()
{
const goals = await fetchApi('campaigns', {
'fields[goal]': 'amount_cents,completed_percentage,created_at,description,reached_at,title',
'include': 'goals',
});
return goals;
}
/**
* Helper function for returning a JSON response.
*
* @param {Object} input
* @param {Number} statusCode
* @returns {Response}
*/
function response(input, statusCode = 200)
{
return new Response(JSON.stringify(input), {
headers: {
'Content-Type': 'application/json',
},
status: statusCode,
});
}
async function handleRequest(request) {
const url = new URL(request.url);
if (url.pathname === '/goals') {
return response(await getGoals());
}
return response(
{
error: 'Not found',
},
404
);
}
addEventListener('fetch', function(event) {
return event.respondWith(handleRequest(event.request))
});

17
worker/package.json Normal file
View File

@ -0,0 +1,17 @@
{
"private": true,
"name": "patreon-api-proxy",
"version": "1.0.0",
"description": "Cloudflare Worker that works as a basic API proxy, so we don't have to deal with refreshing tokens in WordPress",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"format": "prettier --write '**/*.{js,css,json,md}'"
},
"author": "Alex Thomassen <alex@thomassen.xyz>",
"license": "MIT",
"devDependencies": {
"prettier": "^1.18.2"
}
}

View File

@ -0,0 +1,14 @@
name = "patreon-api-proxy"
account_id = ""
workers_dev = true
compatibility_date = "2022-09-10"
main = "index.js"
[vars]
PATREON_CLIENT_ID = ""
PATREON_CLIENT_SECRET = ""
[[kv_namespaces]]
binding = "PATREON_TOKENS"
id = ""
preview_id = ""