mirror of
https://github.com/standardebooks/web.git
synced 2025-07-15 19:06:49 -04:00
Add newsletter management functionality
This commit is contained in:
parent
90ee0a93c9
commit
b0197d189a
57 changed files with 1017 additions and 143 deletions
|
@ -19,7 +19,7 @@ try{
|
|||
Logger::WriteGithubWebhookLogEntry($requestId, 'Received GitHub webhook.');
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] != 'POST'){
|
||||
throw new WebhookException('Expected HTTP POST.');
|
||||
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
||||
}
|
||||
|
||||
$post = file_get_contents('php://input') ?: '';
|
||||
|
@ -30,12 +30,12 @@ try{
|
|||
$hash = $splitHash[1];
|
||||
|
||||
if(!hash_equals($hash, hash_hmac($hashAlgorithm, $post, preg_replace("/[\r\n]/ius", '', file_get_contents(GITHUB_SECRET_FILE_PATH) ?: '') ?? ''))){
|
||||
throw new WebhookException('Invalid GitHub webhook secret.', $post);
|
||||
throw new Exceptions\InvalidCredentialsException();
|
||||
}
|
||||
|
||||
// Sanity check before we continue.
|
||||
if(!array_key_exists('HTTP_X_GITHUB_EVENT', $_SERVER)){
|
||||
throw new WebhookException('Couldn\'t understand HTTP request.', $post);
|
||||
throw new Exceptions\WebhookException('Couldn\'t understand HTTP request.', $post);
|
||||
}
|
||||
|
||||
$data = json_decode($post, true);
|
||||
|
@ -45,20 +45,20 @@ try{
|
|||
case 'ping':
|
||||
// Silence on success.
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Event type: ping.');
|
||||
throw new NoopException();
|
||||
throw new Exceptions\NoopException();
|
||||
case 'push':
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Event type: push.');
|
||||
|
||||
// Get the ebook ID. PHP doesn't throw exceptions on invalid array indexes, so check that first.
|
||||
if(!array_key_exists('repository', $data) || !array_key_exists('name', $data['repository'])){
|
||||
throw new WebhookException('Couldn\'t understand HTTP POST data.', $post);
|
||||
throw new Exceptions\WebhookException('Couldn\'t understand HTTP POST data.', $post);
|
||||
}
|
||||
|
||||
$repoName = trim($data['repository']['name'], '/');
|
||||
|
||||
if(in_array($repoName, GITHUB_IGNORED_REPOS)){
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Repo is in ignore list, no action taken.');
|
||||
throw new NoopException();
|
||||
throw new Exceptions\NoopException();
|
||||
}
|
||||
|
||||
// Get the filesystem path for the ebook.
|
||||
|
@ -73,7 +73,7 @@ try{
|
|||
}
|
||||
|
||||
if(!file_exists($dir . '/HEAD')){
|
||||
throw new WebhookException('Couldn\'t find repo "' . $repoName . '" in filesystem at "' . $dir . '".', $post);
|
||||
throw new Exceptions\WebhookException('Couldn\'t find repo "' . $repoName . '" in filesystem at "' . $dir . '".', $post);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,13 +84,13 @@ try{
|
|||
|
||||
if($lastCommitSha1 == ''){
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Error getting last local commit. Output: ' . $lastCommitSha1);
|
||||
throw new WebhookException('Couldn\'t process ebook.', $post);
|
||||
throw new Exceptions\WebhookException('Couldn\'t process ebook.', $post);
|
||||
}
|
||||
else{
|
||||
if($data['after'] == $lastCommitSha1){
|
||||
// This commit is already in our local repo, so silent success
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Local repo already in sync, no action taken.');
|
||||
throw new NoopException();
|
||||
throw new Exceptions\NoopException();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ try{
|
|||
exec('sudo --set-home --user se-vcs-bot ' . SITE_ROOT . '/scripts/pull-from-github ' . escapeshellarg($dir) . ' 2>&1', $output, $returnCode);
|
||||
if($returnCode != 0){
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Error pulling from GitHub. Output: ' . implode("\n", $output));
|
||||
throw new WebhookException('Couldn\'t process ebook.', $post);
|
||||
throw new Exceptions\WebhookException('Couldn\'t process ebook.', $post);
|
||||
}
|
||||
else{
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, '`git pull` from GitHub complete.');
|
||||
|
@ -120,7 +120,7 @@ try{
|
|||
exec('sudo --set-home --user se-vcs-bot tsp ' . SITE_ROOT . '/web/scripts/deploy-ebook-to-www' . $lastPushHashFlag . ' ' . escapeshellarg($dir) . ' 2>&1', $output, $returnCode);
|
||||
if($returnCode != 0){
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Error queueing ebook for deployment to web. Output: ' . implode("\n", $output));
|
||||
throw new WebhookException('Couldn\'t process ebook.', $post);
|
||||
throw new Exceptions\WebhookException('Couldn\'t process ebook.', $post);
|
||||
}
|
||||
else{
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Queue for deployment to web complete.');
|
||||
|
@ -128,13 +128,17 @@ try{
|
|||
|
||||
break;
|
||||
default:
|
||||
throw new WebhookException('Unrecognized GitHub webhook event.', $post);
|
||||
throw new Exceptions\WebhookException('Unrecognized GitHub webhook event.', $post);
|
||||
}
|
||||
|
||||
// "Success, no content"
|
||||
http_response_code(204);
|
||||
}
|
||||
catch(WebhookException $ex){
|
||||
catch(Exceptions\InvalidCredentialsException $ex){
|
||||
// "Forbidden"
|
||||
http_response_code(403);
|
||||
}
|
||||
catch(Exceptions\WebhookException $ex){
|
||||
// Uh oh, something went wrong!
|
||||
// Log detailed error and debugging information locally.
|
||||
Logger::WriteGithubWebhookLogEntry($requestId, 'Webhook failed! Error: ' . $ex->getMessage());
|
||||
|
@ -146,7 +150,7 @@ catch(WebhookException $ex){
|
|||
// "Client error"
|
||||
http_response_code(400);
|
||||
}
|
||||
catch(NoopException $ex){
|
||||
catch(Exceptions\NoopException $ex){
|
||||
// We arrive here because a special case required us to take no action for the request, but execution also had to be interrupted.
|
||||
// For example, we received a request for a known repo for which we must ignore requests.
|
||||
|
||||
|
|
87
www/webhooks/postmark.php
Normal file
87
www/webhooks/postmark.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?
|
||||
require_once('Core.php');
|
||||
|
||||
use function Safe\curl_exec;
|
||||
use function Safe\curl_init;
|
||||
use function Safe\curl_setopt;
|
||||
use function Safe\file_get_contents;
|
||||
use function Safe\json_decode;
|
||||
use function Safe\substr;
|
||||
|
||||
// Get a semi-random ID to identify this request within the log.
|
||||
$requestId = substr(sha1(time() . rand()), 0, 8);
|
||||
|
||||
try{
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Received Postmark webhook.');
|
||||
|
||||
if($_SERVER['REQUEST_METHOD'] != 'POST'){
|
||||
throw new Exceptions\WebhookException('Expected HTTP POST.');
|
||||
}
|
||||
|
||||
$apiKey = trim(file_get_contents(SITE_ROOT . '/config/secrets/webhooks@postmarkapp.com')) ?: '';
|
||||
|
||||
// Ensure this webhook actually came from Postmark
|
||||
if($apiKey != ($_SERVER['HTTP_X_SE_KEY'] ?? '')){
|
||||
throw new Exceptions\InvalidCredentialsException();
|
||||
}
|
||||
|
||||
$post = json_decode(file_get_contents('php://input') ?: '');
|
||||
|
||||
if(!$post || !property_exists($post, 'RecordType')){
|
||||
throw new Exceptions\WebhookException('Couldn\'t understand HTTP request.', $post);
|
||||
}
|
||||
|
||||
if($post->RecordType == 'SpamComplaint'){
|
||||
// Received when a user marks an email as spam
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Event type: spam complaint.');
|
||||
|
||||
Db::Query('delete from NewsletterSubscribers where Email = ?', [$post->Email]);
|
||||
}
|
||||
elseif($post->RecordType == 'SubscriptionChange' && $post->SuppressSending){
|
||||
// Received when a user clicks Postmark's "Unsubscribe" link in a newsletter email
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Event type: unsubscribe.');
|
||||
|
||||
$email = $post->Recipient;
|
||||
|
||||
// Remove the email from our newsletter list
|
||||
Db::Query('delete from NewsletterSubscribers where Email = ?', [$email]);
|
||||
|
||||
// Remove the suppression from Postmark, since we deleted it from our own list we will never email them again anyway
|
||||
$handle = curl_init();
|
||||
curl_setopt($handle, CURLOPT_URL, 'https://api.postmarkapp.com/message-streams/' . $post->MessageStream . '/suppressions/delete');
|
||||
curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($handle, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Accept: application/json', 'X-Postmark-Server-Token: ' . EMAIL_SMTP_USERNAME]);
|
||||
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, "POST");
|
||||
curl_setopt($handle, CURLOPT_POSTFIELDS, '{"Suppressions": [{"EmailAddress": "' . $email . '"}]}');
|
||||
curl_exec($handle);
|
||||
curl_close($handle);
|
||||
}
|
||||
elseif($post->RecordType == 'SubscriptionChange' && $post->SuppressionReason === null){
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Event type: suppression deletion.');
|
||||
}
|
||||
else{
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Unrecognized event: ' . $post->RecordType);
|
||||
}
|
||||
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Event processed.');
|
||||
|
||||
// "Success, no content"
|
||||
http_response_code(204);
|
||||
}
|
||||
catch(Exceptions\InvalidCredentialsException $ex){
|
||||
// "Forbidden"
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Invalid key: ' . ($_SERVER['HTTP_X_SE_KEY'] ?? ''));
|
||||
http_response_code(403);
|
||||
}
|
||||
catch(Exceptions\WebhookException $ex){
|
||||
// Uh oh, something went wrong!
|
||||
// Log detailed error and debugging information locally.
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Webhook failed! Error: ' . $ex->getMessage());
|
||||
Logger::WritePostmarkWebhookLogEntry($requestId, 'Webhook POST data: ' . $ex->PostData);
|
||||
|
||||
// Print less details to the client.
|
||||
print($ex->getMessage());
|
||||
|
||||
// "Client error"
|
||||
http_response_code(400);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue