Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patreon and analytics #55

Merged
merged 18 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 5 additions & 23 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database

# compiler environment: nsjail, or local (default)
COMPILER_ENVIRONMENT=nsjail
# compiler caching: true or false (default)
COMPILER_CACHING=true

BCRYPT_ROUNDS=12
Expand All @@ -25,11 +23,6 @@ LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
Expand All @@ -44,26 +37,15 @@ QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_PREFIX=

MEMCACHED_HOST=127.0.0.1

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"

PATREON_ACCESS_TOKEN=
PATREON_WEBHOOK_SECRET=

CLOUDFLARE_ANALYTICS_TOKEN=
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. Each batch

It is a summary of changes that would be pertinent to the end user of the PGEtinker website. For a comprehensive history of changes made to the project, please refer to the repository's commit history.

## 2024-05-06

- Added cloudflare analytics and disclosure
- Added supporter wall

## 2024-05-05

- Added logging to database
Expand Down
42 changes: 42 additions & 0 deletions app/Console/Commands/Patreon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Console\Commands;

use App\Http\Controllers\PatreonController;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

class Patreon extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:patreon';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Patreon Supporters';

/**
* Execute the console command.
*/
public function handle()
{
if(empty(env("PATREON_ACCESS_TOKEN")))
{
Log::error("Error: missing Patreon Access Token... aborted.");
return;
}

$controller = new PatreonController();
$controller->getPatreonNames();

}
}
101 changes: 101 additions & 0 deletions app/Http/Controllers/PatreonController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

namespace App\Http\Controllers;

use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

class PatreonController extends Controller
{
function update(Request $request)
{
$disk = (!empty(env("AWS_BUCKET"))) ? Storage::disk("s3") : Storage::disk("local");

if(empty(env("PATREON_ACCESS_TOKEN")) || empty(env("PATREON_WEBHOOK_SECRET")))
{
Log::error("Error: missing Patreon Access Token or Webhook Secret... aborted.");

return response([
"statusCode" => 500,
"message" => "Missing Patreon Access Token or Webhook Secret.",
], 500);
}

$signature = hash_hmac("md5", $request->getContent(), env("PATREON_WEBHOOK_SECRET"));
if($request->header("X-Patreon-Signature") != $signature)
{
Log::alert("Unauthorized webhook request");
return response([
"statusCode" => 401,
"message" => "unauthorized"
]);
}

$this->getPatreonNames();

return [];
}

function getPatreonNames()
{
$disk = (!empty(env("AWS_BUCKET"))) ? Storage::disk("s3") : Storage::disk("local");

Log::info("Getting Patreon Supporters");

$campaign = Http::withToken(env("PATREON_ACCESS_TOKEN"))
->get("https://www.patreon.com/api/oauth2/v2/campaigns")
->json()["data"][0]["id"];

$cursor = null;
$keepGoing = true;

$supporters = [];
while($keepGoing)
{
$membersRequest = Http::withToken(env("PATREON_ACCESS_TOKEN"));
$membersRequest->withQueryParameters([
"page[count]" => 25,
"include" => "currently_entitled_tiers",
"fields[member]" => "full_name,last_charge_date,last_charge_status,currently_entitled_amount_cents,patron_status",
]);
if($cursor != null)
{
$membersRequest->withQueryParameters([
"page[cursor]" => $cursor,
]);
}

$members = $membersRequest->get("https://www.patreon.com/api/oauth2/v2/campaigns/{$campaign}/members")->json();

if(count($members["data"]) > 0)
{
foreach($members["data"] as $member)
{
if($member["attributes"]["currently_entitled_amount_cents"] >= 500)
{
if(!empty($member["attributes"]["full_name"]))
{
$supporters[] = [
"amount" => $member["attributes"]["currently_entitled_amount_cents"],
"name" => $member["attributes"]["full_name"],
];
}
}
}
}

$cursor = $members["meta"]["pagination"]["cursors"]["next"];
if(empty($cursor))
{
$keepGoing = false;
}
}

$disk->put("supporters.json", json_encode(["supporters" => $supporters]));
}
}


30 changes: 30 additions & 0 deletions resources/css/dialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,33 @@
content: "✔ Fixed: ";
color: rgb(252, 252, 170);
}

.dialog.supporters h3 {
text-align: center;
font-size: 2rem;
// text-decoration: underline;
padding-bottom: 1rem;
border-bottom: 4px solid var(--dialog-text-color);
}

.dialog.supporters .names {
display: flex;
width: 100%;
flex-direction: column;
justify-content: center;
// justify-items: center;
align-items: center;
}

.dialog.supporters .names .name {
padding: .5rem 2rem;
font-size: 2.5rem;
}

.dialog.supporters a {
display: block;
text-align: center;
margin-top: 2rem;
margin-bottom: 1rem;
font-size: 2.5rem;
}
3 changes: 2 additions & 1 deletion resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import agreeDialog from './lib/agreeDialog';
import shareDialog from './lib/shareDialog';
import newsDialog from './lib/newsDialog';
import defaultLayout from './lib/defaultLayout';
import supportersDialog from './lib/supportersDialog';

class PGEtinker
{
Expand Down Expand Up @@ -192,7 +193,7 @@ class PGEtinker
document.querySelector("#supporters").addEventListener("click", (event) =>
{
event.preventDefault();
alert("Not Implemented");
supportersDialog();
});

document.querySelector("#news-and-updates").addEventListener("click", (event) =>
Expand Down
8 changes: 7 additions & 1 deletion resources/js/lib/agreeDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ export default function agreeDialog()
<li>
Share your code worldwide. This only applies if you
use the "Share" functionality.
</li>
</li>
<li>
To use <a href="https://developers.cloudflare.com/analytics/web-analytics/" target="_blank">
passive monitoring analytics</a> for the purpose
of identifying problems with the app and improving
the overall experience.
</li>
</ul>
</div>
<div class="footer">
Expand Down
66 changes: 66 additions & 0 deletions resources/js/lib/supportersDialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export default function supportersDialog()
{
function supportersClickAnywhereHandler(event)
{
let supportersDialog = document.querySelector(".dialog.supporters");
if(supportersDialog == null)
return;

if(event.target.tagName == 'A')
return;

supportersDialog.dispatchEvent(new Event("close-dialog"));
}

return new Promise((resolve) =>
{
let supportersDialog = document.createElement("div");

supportersDialog.classList.toggle("dialog", true);
supportersDialog.classList.toggle("supporters", true);

axios.get("/api/supporters").then((response) =>
{

let entries = [];

if(response.data.supporters.length > 0)
{
response.data.supporters.forEach((entry) =>
{
entries.push(`<div class="name">◀ ${entry} ▶</div>`);
});
}
else
{
entries.push(`<div class="name">◀ No Supporters Yet ▶</div>`);
}

supportersDialog.innerHTML = `
<div class="window">
<div class="header">Patreon Supporters!</div>
<div class="content">
<h3>PGEtinker would not exist without the support of:</h3>
<div class="names">
${entries.join('')}
</div>
<a target="_blank" href="https://patreon.com/PGEtinker">
Become a Supporter
</a>
</div>
</div>`;

supportersDialog.addEventListener("close-dialog", (event) =>
{
setTimeout(() => window.removeEventListener("click", supportersClickAnywhereHandler), 500);
supportersDialog.remove();
resolve();
});

setTimeout(() => window.addEventListener("click", supportersClickAnywhereHandler), 500);
document.body.appendChild(supportersDialog);
});

});

}
3 changes: 3 additions & 0 deletions resources/views/disagree.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@


</div>

@include("shared.analytics")

</body>
</html>
4 changes: 3 additions & 1 deletion resources/views/home.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
@vite('resources/js/app.js')
</head>
<body>

<div id="app">
<nav id="header">
<a class="brand" href="/">
Expand Down Expand Up @@ -119,5 +118,8 @@
</div>
</div>
<textarea id="code" style="display: none;">{{ $code }}</textarea>

@include("shared.analytics")

</body>
</html>
4 changes: 4 additions & 0 deletions resources/views/shared/analytics.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

@if( !empty(env("CLOUDFLARE_ANALYTICS_TOKEN")))
<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "{{ env("CLOUDFLARE_ANALYTICS_TOKEN") }}"}'></script><!-- End Cloudflare Web Analytics -->
@endif
Loading