Added in a cookie popup and proper newsletter functionality
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 21s
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 21s
Signed-off-by: rodude123 <rodude123@gmail.com>
This commit is contained in:
Vendored
+843
-9
@@ -8,11 +8,15 @@ use DOMDocument;
|
||||
use PDO;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use DonatelloZa\RakePlus\RakePlus;
|
||||
use function DI\string;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
use function api\utils\dbConn;
|
||||
use function api\utils\getEmailPassword;
|
||||
use const api\utils\feedGenerator\ATOM;
|
||||
use const api\utils\feedGenerator\RSS2;
|
||||
|
||||
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
require_once __DIR__ . "/../utils/imgUtils.php";
|
||||
require_once __DIR__ . "/../utils/feedGenerator/FeedWriter.php";
|
||||
@@ -254,10 +258,10 @@ class blogData
|
||||
* @param string $dateCreated - Date the blog post was created
|
||||
* @param bool $featured - Whether the blog post is featured or not
|
||||
* @param string $categories - Categories of the blog post
|
||||
* @param UploadedFileInterface $headerImg - Header image of the blog post
|
||||
* @param UploadedFileInterface|null $headerImg - Header image of the blog post
|
||||
* @return int|string - ID of the blog post or error message
|
||||
*/
|
||||
public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string
|
||||
public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface|null $headerImg): int|string
|
||||
{
|
||||
$conn = dbConn();
|
||||
$folderID = uniqid();
|
||||
@@ -289,6 +293,13 @@ class blogData
|
||||
|
||||
$keywords = implode(", ", RakePlus::create($bodyText)->keywords());
|
||||
|
||||
$latest = $this->getLatestBlogPost();
|
||||
$prevTitle = $latest["title"];
|
||||
$prevAbstract = $latest["abstract"];
|
||||
$prevHeaderImage = $latest["headerImg"];
|
||||
|
||||
$headerImage = $targetFile["imgLocation"];
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, keywords, folderID)
|
||||
VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :bodyText, :categories, :keywords, :folderID);");
|
||||
$stmt->bindParam(":title", $title);
|
||||
@@ -296,7 +307,7 @@ class blogData
|
||||
$stmt->bindParam(":dateModified", $dateCreated);
|
||||
$isFeatured = $featured ? 1 : 0;
|
||||
$stmt->bindParam(":featured", $isFeatured);
|
||||
$stmt->bindParam(":headerImg", $targetFile["imgLocation"]);
|
||||
$stmt->bindParam(":headerImg", $headerImage);
|
||||
$stmt->bindParam(":abstract", $abstract);
|
||||
$stmt->bindParam(":body", $newBody);
|
||||
$stmt->bindParam(":bodyText", $bodyText);
|
||||
@@ -304,12 +315,267 @@ class blogData
|
||||
$stmt->bindParam(":keywords", $keywords);
|
||||
$stmt->bindParam(":folderID", $folderID);
|
||||
|
||||
if ($stmt->execute())
|
||||
if (!$stmt->execute())
|
||||
{
|
||||
return intval($conn->lastInsertId());
|
||||
return "Error, couldn't create post";
|
||||
}
|
||||
|
||||
return "Error, couldn't create post";
|
||||
$stmtEmails = $conn->prepare("SELECT email FROM newsletter;");
|
||||
$stmtEmails->execute();
|
||||
$emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$emailBody = <<<EOD
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Rohit Pai's blog</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/notosans/v34/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Share Tech Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sharetechmono/v15/J7aHnp1uDWRBEqV98dVQztYldFcLowEF.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Noto Sans KR, sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
line-height: 1.625rem;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
main, header, footer {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-family: Share Tech Mono, monospace;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 2.5625rem;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
h1, nav {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
header {
|
||||
background: hsla(80, 60%, 50%, 1);
|
||||
color: #FFFFFF;
|
||||
border-bottom: 5px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
header div.title, header div.byLine {
|
||||
padding: 0 3em 1em;
|
||||
}
|
||||
|
||||
header div.title h1 {
|
||||
margin: 0;
|
||||
padding: 1em 0 0;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
header div.byLine {
|
||||
border-top: 5px solid hsla(80, 60%, 30%, 1);
|
||||
}
|
||||
|
||||
a.btn {
|
||||
background-color: hsla(80, 60%, 50%, 1);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
padding: 1em 2em;
|
||||
border-radius: 0.625em;
|
||||
border: 0.3215em solid hsla(80, 60%, 50%, 1);
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
max-height: 4em;
|
||||
}
|
||||
|
||||
div.postContainer, div.container {
|
||||
padding: 1em 2em 0;
|
||||
margin: 0 auto 1em;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
div.postContainer ~ div.postContainer {
|
||||
border-top: 5px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.postContainer > *, div.container > * {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
div.postContainer div.post h2, div.container div.content h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.postContainer div.image, div.container div.image {
|
||||
width: 50%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
div.postContainer div.image img {
|
||||
-webkit-border-radius: 0.5em;
|
||||
-moz-border-radius: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
border: 4px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.container div.image img {
|
||||
-webkit-border-radius: 10em;
|
||||
-moz-border-radius: 10em;
|
||||
border-radius: 10em;
|
||||
border: 4px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.postContainer div.image img, div.container div.image img {
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: hsla(80, 60%, 50%, 1);
|
||||
margin-top: auto;
|
||||
padding: 3em;
|
||||
display: block;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer .nav {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
footer .nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer .nav ul li {
|
||||
display: inline-block;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
footer .nav ul a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer .date {
|
||||
width: 25%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="title">
|
||||
<h1>Hey, I've got a new post!</h1>
|
||||
</div>
|
||||
<div class="byLine">
|
||||
<p>Hello I'm Rohit an avid full-stack developer and home lab enthusiast. I love anything and everything
|
||||
to do with full stack development, home labs, coffee and generally anything to do with tech.</p>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="postContainer">
|
||||
<h2>latest post</h2>
|
||||
<div class="image">
|
||||
<img src="$headerImage" alt="header image of the latest post">
|
||||
</div>
|
||||
|
||||
<div class="post">
|
||||
<h3>$title</h3>
|
||||
<p>$abstract</p>
|
||||
<a href="https://rohitpai.co.uk/blog/post/$title" class="btn">See Post</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="postContainer">
|
||||
<h2>in case you missed the previous post</h2>
|
||||
<div class="image">
|
||||
<img src="$prevHeaderImage" alt="header image of the previous post">
|
||||
</div>
|
||||
|
||||
<div class="post">
|
||||
<h3>$prevTitle</h3>
|
||||
<p>$prevAbstract</p>
|
||||
<a href="https://rohitpai.co.uk/blog/post/$prevTitle" class="btn">See Post</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li><a href="https://rohitpai.co.uk/blog"><https://rohitpai.co.uk/blog></a></li>
|
||||
<li><a href="https://rohitpai.co.uk/blog/unsubscribe"><Unsubscribe></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="date">2023</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
|
||||
foreach ($emails as $email)
|
||||
{
|
||||
$this->sendMail($email["email"], $emailBody, "Hey, Rohit's blog has a new post!");
|
||||
}
|
||||
|
||||
return intval($conn->lastInsertId());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -616,12 +882,12 @@ class blogData
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
$items[] = array(
|
||||
"id" => string($post["ID"]),
|
||||
"id" => strval($post["ID"]),
|
||||
"url" => "https://rohitpai.co.uk/blog/post/" . rawurlencode($post["title"]) . "#disqus_thread",
|
||||
"title" => $post["title"],
|
||||
"date_published" => date($post["dateCreated"]),
|
||||
"date_modified" => date($post["dateModified"]),
|
||||
// "description" => $post["abstract"],
|
||||
"description" => $post["abstract"],
|
||||
"banner_image" => "https://rohitpai.co.uk/" . rawurlencode($post["headerImg"]),
|
||||
"content_html" => $post["body"]
|
||||
);
|
||||
@@ -655,4 +921,572 @@ class blogData
|
||||
|
||||
return $feed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an email to the newsletter and send welcome email
|
||||
* @param string $email - Email to add to the newsletter
|
||||
* @return string|array - Success or error message
|
||||
*/
|
||||
public function addNewsletterEmail(string $email): string|array
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;");
|
||||
$stmtCheckEmail->bindParam(":email", $email);
|
||||
$stmtCheckEmail->execute();
|
||||
$result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
return "Email already exists";
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO newsletter (email) VALUES (:email);");
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();
|
||||
|
||||
$body = <<<EOD
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Rohit Pai's blog</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/notosans/v34/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Share Tech Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sharetechmono/v15/J7aHnp1uDWRBEqV98dVQztYldFcLowEF.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Noto Sans KR, sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
line-height: 1.625rem;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
main, header, footer {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-family: Share Tech Mono, monospace;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 2.5625rem;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
h1, nav {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
header {
|
||||
background: hsla(80, 60%, 50%, 1);
|
||||
color: #FFFFFF;
|
||||
border-bottom: 5px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
header div.title, header div.byLine {
|
||||
padding: 0 3em 1em;
|
||||
}
|
||||
|
||||
header div.title h1 {
|
||||
margin: 0;
|
||||
padding: 1em 0 0;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
header div.byLine {
|
||||
border-top: 5px solid hsla(80, 60%, 30%, 1);
|
||||
}
|
||||
|
||||
a.btn {
|
||||
background-color: hsla(80, 60%, 50%, 1);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
padding: 1em 2em;
|
||||
border-radius: 0.625em;
|
||||
border: 0.3215em solid hsla(80, 60%, 50%, 1);
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
max-height: 4em;
|
||||
}
|
||||
|
||||
div.postContainer, div.container {
|
||||
padding: 1em 2em 0;
|
||||
margin: 0 auto 1em;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
div.postContainer ~ div.postContainer {
|
||||
border-top: 5px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.postContainer > *, div.container > * {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
div.postContainer div.post h2, div.container div.content h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.postContainer div.image, div.container div.image {
|
||||
width: 50%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
div.postContainer div.image img {
|
||||
-webkit-border-radius: 0.5em;
|
||||
-moz-border-radius: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
border: 4px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.container div.image img {
|
||||
-webkit-border-radius: 50em;
|
||||
-moz-border-radius: 50em;
|
||||
border-radius: 50em;
|
||||
border: 4px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.postContainer div.image img, div.container div.image img {
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: hsla(80, 60%, 50%, 1);
|
||||
margin-top: auto;
|
||||
padding: 3em;
|
||||
display: block;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer .nav {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
footer .nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer .nav ul li {
|
||||
display: inline-block;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
footer .nav ul a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer .date {
|
||||
width: 25%;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="title">
|
||||
<h1>hello from rohit</h1>
|
||||
</div>
|
||||
<div class="byLine">
|
||||
<p>Hello I'm Rohit an avid full-stack developer and home lab enthusiast. I love anything and everything
|
||||
to do with full stack development, home labs, coffee and generally anything to do with tech.</p>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="container">
|
||||
<h2>hey there, i'm rohit!</h2>
|
||||
<div class="image"><img src="https://rohitpai.co.uk/imgs/profile.jpg" alt=""></div>
|
||||
<div class="content">
|
||||
<h3>What to Expect</h3>
|
||||
<p>You'll get an email from me everytime I make a new post to my blog. Sometimes, you may get a special
|
||||
email on occasion, where I talk about something interesting that's not worth a full on post but I
|
||||
Still want to tell you about it.</p>
|
||||
<p>Don't worry, I won't spam you with emails. Well, thanks for signing up to my newsletter, hopefully
|
||||
you'll hear from me soon!</p>
|
||||
<p>P.S. Please consider adding this email address rohit@rohitpai.co.uk to your emails
|
||||
contact list so that my emails won't get sent to span, thanks.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li><a href="https://rohitpai.co.uk/blog"><https://rohitpai.co.uk/blog></a></li>
|
||||
<li><a href="https://rohitpai.co.uk/blog/unsubscribe"><Unsubscribe></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="date">2023</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
|
||||
return $this->sendMail($email, $body, "Hello from Rohit!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email to the given email address
|
||||
* @param string $email - Email address to send the email to
|
||||
* @param string $body - Body of the email
|
||||
* @param string $subject - Subject of the email
|
||||
* @return string|string[]
|
||||
*/
|
||||
public function sendMail(string $email, string $body, string $subject): string|array
|
||||
{
|
||||
$mail = new PHPMailer(true);
|
||||
try
|
||||
{
|
||||
$mail->isSMTP();
|
||||
$mail->Host = "smtp.hostinger.com";
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = "rohit@rohitpai.co.uk";
|
||||
$mail->Password = getEmailPassword();
|
||||
$mail->SMTPSecure = "tls";
|
||||
$mail->Port = 587;
|
||||
$mail->setFrom("rohit@rohitpai.co.uk", "Rohit Pai");
|
||||
$mail->addAddress($email);
|
||||
$mail->isHTML();
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
$mail->send();
|
||||
return "success";
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
return array("errorMessage" => "Error, couldn't send email because of " . $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email - Email to delete from the newsletter
|
||||
* @return string - Success or error message
|
||||
*/
|
||||
public function deleteNewsletterEmail(string $email): string
|
||||
{
|
||||
$conn = dbConn();
|
||||
|
||||
$stmtCheckEmail = $conn->prepare("SELECT * FROM newsletter WHERE email = :email;");
|
||||
$stmtCheckEmail->bindParam(":email", $email);
|
||||
$stmtCheckEmail->execute();
|
||||
$result = $stmtCheckEmail->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result)
|
||||
{
|
||||
return "email not found";
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("DELETE FROM newsletter WHERE email = :email;");
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();;
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $subject - Subject of the newsletter
|
||||
* @param string $message - Message content
|
||||
* @return array|string - Success or error message
|
||||
*/
|
||||
public function sendNewsletter(string $subject, string $message): array|string
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmtEmails = $conn->prepare("SELECT email FROM newsletter;");
|
||||
$stmtEmails->execute();
|
||||
$emails = $stmtEmails->fetchAll(PDO::FETCH_ASSOC);
|
||||
$msg = "";
|
||||
|
||||
$body = <<<EOD
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Rohit Pai's blog</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/notosans/v34/o-0NIpQlx3QUlC5A4PNjXhFVZNyB.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Share Tech Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(https://fonts.gstatic.com/s/sharetechmono/v15/J7aHnp1uDWRBEqV98dVQztYldFcLowEF.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Noto Sans KR, sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
line-height: 1.625rem;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
main, header, footer {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
font-family: Share Tech Mono, monospace;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 2.5625rem;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
h1, nav {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
header {
|
||||
background: hsla(80, 60%, 50%, 1);
|
||||
color: #FFFFFF;
|
||||
border-bottom: 5px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
header div.title, header div.byLine {
|
||||
padding: 0 3em 1em;
|
||||
}
|
||||
|
||||
header div.title h1 {
|
||||
margin: 0;
|
||||
padding: 1em 0 0;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
header div.byLine {
|
||||
border-top: 5px solid hsla(80, 60%, 30%, 1);
|
||||
}
|
||||
|
||||
a.btn {
|
||||
background-color: hsla(80, 60%, 50%, 1);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
padding: 1em 2em;
|
||||
border-radius: 0.625em;
|
||||
border: 0.3215em solid hsla(80, 60%, 50%, 1);
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
max-height: 4em;
|
||||
}
|
||||
|
||||
div.postContainer, div.container {
|
||||
padding: 1em 2em 0;
|
||||
margin: 0 auto 1em;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
div.postContainer ~ div.postContainer {
|
||||
border-top: 5px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.postContainer > *, div.container > * {
|
||||
margin: 0 auto;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
div.postContainer div.post h2, div.container div.content h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.postContainer div.image, div.container div.image {
|
||||
width: 50%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
div.postContainer div.image img {
|
||||
-webkit-border-radius: 0.5em;
|
||||
-moz-border-radius: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
border: 4px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.container div.image img {
|
||||
-webkit-border-radius: 50em;
|
||||
-moz-border-radius: 50em;
|
||||
border-radius: 50em;
|
||||
border: 4px solid hsla(0, 0%, 75%, 1);
|
||||
}
|
||||
|
||||
div.postContainer div.image img, div.container div.image img {
|
||||
min-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: hsla(80, 60%, 50%, 1);
|
||||
margin-top: auto;
|
||||
padding: 3em;
|
||||
display: block;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer .nav {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
footer .nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
footer .nav ul li {
|
||||
display: inline-block;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
footer .nav ul a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer .date {
|
||||
width: 25%;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="title">
|
||||
<h1>a surprise hello from rohit</h1>
|
||||
</div>
|
||||
<div class="byLine">
|
||||
<p>Hello I'm Rohit an avid full-stack developer and home lab enthusiast. I love anything and everything
|
||||
to do with full stack development, home labs, coffee and generally anything to do with tech.</p>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="container">
|
||||
<h2>$subject</h2>
|
||||
<div class="content">
|
||||
$message
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li><a href="https://rohitpai.co.uk/blog"><https://rohitpai.co.uk/blog></a></li>
|
||||
<li><a href="https://rohitpai.co.uk/blog/unsubscribe"><Unsubscribe></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="date">2023</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
|
||||
|
||||
foreach ($emails as $email)
|
||||
{
|
||||
$msg = $this->sendMail($email["email"], $body, $subject);
|
||||
if (is_array($msg))
|
||||
{
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
Vendored
+82
-4
@@ -269,12 +269,38 @@ class blogRoutes implements routesInterface
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->delete("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["email"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->deleteNewsletterEmail($args["email"]);
|
||||
|
||||
if ($message === "email not found")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, email not found")));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
if ($message === "error")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->post("/blog/post", function (Request $request, Response $response)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
$files = $request->getUploadedFiles();
|
||||
$headerImg = $files["headerImg"];
|
||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
@@ -288,6 +314,11 @@ class blogRoutes implements routesInterface
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (array_key_exists("headerImg", $files))
|
||||
{
|
||||
$headerImg = $files["headerImg"];
|
||||
}
|
||||
|
||||
if (empty($files["headerImg"]))
|
||||
{
|
||||
$headerImg = null;
|
||||
@@ -339,19 +370,66 @@ class blogRoutes implements routesInterface
|
||||
if (empty($files))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent"))));
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]);
|
||||
if (!is_array($message))
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => array("message" => $message))));
|
||||
$response->getBody()->write(json_encode(array("error" => $message)));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($message));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
$app->post("/blog/newsletter", function (Request $request, Response $response)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
if (empty($data["subject"]) || empty($data["message"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->sendNewsletter(strtolower($data["subject"]), $data["message"]);
|
||||
if (is_array($message))
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Message sent")));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
$app->post("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["email"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->addNewsletterEmail($args["email"]);
|
||||
if ($message === "Email already exists")
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("message" => "exists")));
|
||||
return $response->withStatus(409);
|
||||
}
|
||||
|
||||
if (is_array($message) || !$message || $message === "error")
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("message" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Thanks for signing up!")));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -5,6 +5,7 @@ namespace api\project;
|
||||
use api\utils\imgUtils;
|
||||
use PDO;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use function api\utils\dbConn;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
require_once __DIR__ . "/../utils/imgUtils.php";
|
||||
|
||||
Vendored
+1
@@ -3,6 +3,7 @@
|
||||
namespace api\timeline;
|
||||
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
|
||||
Vendored
+2
@@ -4,6 +4,8 @@ namespace api\user;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
use function api\utils\getSecretKey;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
|
||||
Vendored
+23
-3
@@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException;
|
||||
use Slim\Exception\HttpMethodNotAllowedException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Psr7\Response;
|
||||
use Throwable;
|
||||
use Tuupola\Middleware\JwtAuthentication;
|
||||
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
||||
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
||||
@@ -84,8 +85,8 @@ class middleware
|
||||
$app->add(new JwtAuthentication([
|
||||
"rules" => [
|
||||
new RequestPathRule([
|
||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
|
||||
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"]
|
||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"],
|
||||
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
|
||||
]),
|
||||
new RequestMethodRule([
|
||||
"ignore" => ["OPTIONS", "GET"]
|
||||
@@ -133,8 +134,27 @@ class middleware
|
||||
return $response;
|
||||
}
|
||||
});
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
|
||||
|
||||
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
|
||||
|
||||
|
||||
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
|
||||
|
||||
$errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception,
|
||||
bool $displayErrorDetails,
|
||||
bool $logErrors,
|
||||
bool $logErrorDetails
|
||||
) use ($app, $errorHandler)
|
||||
{
|
||||
$statusCode = $exception->getCode() ?: 500;
|
||||
|
||||
// Create a JSON response with the error message
|
||||
$response = $app->getResponseFactory()->createResponse($statusCode);
|
||||
$response->getBody()->write(json_encode(['error' => $exception->getMessage()]));
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace api\user;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
use function api\utils\getSecretKey;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
/**
|
||||
* User Class
|
||||
* Define all functions which either check, update or delete userData data
|
||||
*/
|
||||
class userData
|
||||
{
|
||||
/**
|
||||
* Check if userData exists and can be logged in
|
||||
* @param $username string - Username
|
||||
* @param $password string - Password
|
||||
* @return bool - True if logged in, false if not
|
||||
*/
|
||||
public function checkUser(string $username, string $password): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username");
|
||||
$stmt->bindParam(":username", $username);
|
||||
$stmt->execute();
|
||||
|
||||
// set the resulting array to associative
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
if (password_verify($password, $result[0]["password"]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JWT token
|
||||
* @param $username string - Username
|
||||
* @return string - JWT token
|
||||
*/
|
||||
public function createToken(string $username): string
|
||||
{
|
||||
$now = time();
|
||||
$future = strtotime('+2 day', $now);
|
||||
$secretKey = getSecretKey();
|
||||
$payload = [
|
||||
"jti" => $username,
|
||||
"iat" => $now,
|
||||
"exp" => $future
|
||||
];
|
||||
|
||||
return JWT::encode($payload, $secretKey, "HS256");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if email is already in use
|
||||
* @param string $email - Email to check
|
||||
* @return bool - True if email exists, false if not
|
||||
*/
|
||||
public function checkEmail(string $email): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE email = :email");
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();
|
||||
|
||||
// set the resulting array to associative
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a verification email to the userData
|
||||
* @param $email - email address of the userData
|
||||
* @return string - verification code
|
||||
*/
|
||||
public function sendResetEmail($email): string
|
||||
{
|
||||
//generate a random token and email the address
|
||||
$token = uniqid("rpe-");
|
||||
$headers1 = "From: noreply@rohitpai.co.uk\r\n";
|
||||
$headers1 .= "MIME-Version: 1.0\r\n";
|
||||
$headers1 .= "Content-Type: text/html; charset=UTF-8\r\n";
|
||||
|
||||
$message = "
|
||||
<!doctype html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, userData-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'>
|
||||
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Reset Password Verification Code</h1>
|
||||
<br>
|
||||
<p>Please enter the following code to reset your password: $token</p>
|
||||
</body>
|
||||
</html>
|
||||
";
|
||||
|
||||
mail($email, "Reset Password Verification Code", $message, $headers1);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change password for an email with new password
|
||||
* @param $email string Email
|
||||
* @param $password string Password
|
||||
* @return bool - true if the password was changed, false if not
|
||||
*/
|
||||
public function changePassword(string $email, string $password): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("UPDATE users SET password = :password WHERE email = :email");
|
||||
$newPwd = password_hash($password, PASSWORD_BCRYPT);
|
||||
$stmt->bindParam(":password", $newPwd);
|
||||
$stmt->bindParam(":email", $email);
|
||||
|
||||
if ($stmt->execute())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Vendored
+168
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace api\user;
|
||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||
require_once "userData.php";
|
||||
|
||||
use api\utils\routesInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\App;
|
||||
|
||||
class userRoutes implements routesInterface
|
||||
{
|
||||
private userData $user;
|
||||
|
||||
/**
|
||||
* constructor used to instantiate a base user routes, to be used in the index.php file.
|
||||
* @param App $app - the slim app used to create the routes
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->user = new userData();
|
||||
$this->createRoutes($app);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates the routes for the user
|
||||
* @param App $app - the slim app used to create the routes
|
||||
* @return void - returns nothing
|
||||
*/
|
||||
public function createRoutes(App $app): void
|
||||
{
|
||||
$app->post("/user/login", function (Request $request, Response $response)
|
||||
{
|
||||
// get request data
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (empty($data["username"]) || empty($data["password"]))
|
||||
{
|
||||
// uh oh user sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->checkUser($data["username"], $data["password"]))
|
||||
{
|
||||
// yay, user is logged in
|
||||
$_SESSION["token"] = $this->user->createToken($data["username"]);
|
||||
$_SESSION["username"] = $data["username"];
|
||||
|
||||
$inactive = 60 * 60 * 48; // 2 days
|
||||
$_SESSION["timeout"] = time() + $inactive;
|
||||
|
||||
$response->getBody()->write(json_encode(array("token" => $_SESSION["token"])));
|
||||
return $response;
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->get("/user/logout", function (Request $request, Response $response)
|
||||
{
|
||||
session_unset();
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->get("/user/isLoggedIn", function (Request $request, Response $response)
|
||||
{
|
||||
if (empty($_SESSION["token"]) && empty($_SESSION["username"]))
|
||||
{
|
||||
// uh oh user not logged in
|
||||
return $response->withStatus(401);
|
||||
}
|
||||
|
||||
$inactive = 60 * 60 * 48; // 2 days
|
||||
$sessionLife = time() - $_SESSION["timeout"];
|
||||
if ($sessionLife > $inactive)
|
||||
{
|
||||
// uh oh user session expired
|
||||
session_destroy();
|
||||
return $response->withStatus(401);
|
||||
}
|
||||
|
||||
if (empty($_SESSION["token"]))
|
||||
{
|
||||
// user is logged in but no token was created
|
||||
$_SESSION["token"] = $this->user->createToken($_SESSION["username"]);
|
||||
return $response->withStatus(201);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("token" => $_SESSION["token"])));
|
||||
return $response;
|
||||
|
||||
});
|
||||
|
||||
$app->get("/user/checkResetEmail/{email}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
if (empty($args["email"]))
|
||||
{
|
||||
// uh oh sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->checkEmail($args["email"]))
|
||||
{
|
||||
// yay email does exist
|
||||
$_SESSION["resetToken"] = $this->user->sendResetEmail($args["email"]);
|
||||
$_SESSION["resetEmail"] = $args["email"];
|
||||
return $response;
|
||||
}
|
||||
return $response->withStatus(404);
|
||||
});
|
||||
|
||||
$app->get("/user/resendEmail", function (Request $request, Response $response)
|
||||
{
|
||||
if (empty($_SESSION["resetToken"]))
|
||||
{
|
||||
// uh oh not authorized to resend email
|
||||
return $response->withStatus(401);
|
||||
}
|
||||
|
||||
$_SESSION["resetToken"] = $this->user->sendResetEmail($_SESSION["resetEmail"]);
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->get("/user/checkResetCode/{code}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
if (empty($args["code"]))
|
||||
{
|
||||
// uh oh sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($_SESSION["resetToken"] === $args["code"])
|
||||
{
|
||||
// yay, code code matches
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/changePassword", function (Request $request, Response $response)
|
||||
{
|
||||
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
||||
{
|
||||
// uh oh not authorized to change password
|
||||
return $response->withStatus(401);
|
||||
}
|
||||
|
||||
$data = $request->getParsedBody();
|
||||
if (empty($data["password"]))
|
||||
{
|
||||
// uh oh sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->changePassword($_SESSION["resetEmail"], $data["password"]))
|
||||
{
|
||||
// yay, password changed
|
||||
unset($_SESSION["resetToken"]);
|
||||
unset($_SESSION["resetEmail"]);
|
||||
return $response->withStatus(201);
|
||||
}
|
||||
|
||||
return $response->withStatus(500);
|
||||
});
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Rohit Pai - Blog</title><meta name="title" content="Rohit Pai - Blog"><meta name="description" content="This is all the blog posts that Rohit Pai has posted. You'll find posts on various topics, mostly on tech but some on various other random topics."><meta name="keywords" content="Blog, all posts, rohit, pai, rohit pai, tech, web development, self-hosting, hosting"><meta name="robots" content="index, follow"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="language" content="English"><meta name="author" content="Rohit Pai"><link rel="stylesheet" href="/blog/css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script><script type="text/javascript" src="https://platform-api.sharethis.com/js/sharethis.js#property=6550cdc47a115e0012964576&product=sop" async="async"></script></head><body><nav><input type="checkbox" id="nav-check"><h1><a href="/" class="link">rohit pai</a></h1><div class="nav-btn"><label for="nav-check"><span></span> <span></span> <span></span></label></div><ul><li><a href="/#about" class="textShadow link">about</a></li><li><a href="/#curriculumVitae" class="textShadow link">cv</a></li><li><a href="/#projects" class="textShadow link">projects</a></li><li><a href="/#contact" class="textShadow link">contact</a></li><li><a href="/blog" class="textShadow link active">blog</a></li></ul></nav><header><div><h1>full stack developer</h1><a href="/#sayHello" class="btn btnPrimary boxShadowIn boxShadowOut">Contact Me</a> <a href="" id="arrow"><i class="fa-solid fa-chevron-down"></i></a></div></header><div class="menuBar"><div class="menu"><ul><li><a href="/blog" class="link active">All posts</a></li><li><a href="/blog/category" class="link">categories</a></li><li><label for="searchField" aria-hidden="true" hidden>search</label> <input type="search" name="search" id="searchField" placeholder="Search..."> <button type="submit" id="searchBtn" class="btn btnPrimary"><i class="fa fa-search"></i></button></li></ul></div></div><main id="main"></main><footer class="flexRow"><div class="nav"><ul><li><a href="/blog/policy/privacy" class="link">privacy policy</a></li><li><a href="/blog/policy/cookie" class="link">cookie policy</a></li></ul></div><p>© <span id="year"></span> Rohit Pai all rights reserved</p><div class="button"><button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button></div></footer><script src="/js/typewriter.js"></script><script src="/blog/js/index.js"></script><script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script></body></html>
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Rohit Pai - Blog</title><meta name="title" content="Rohit Pai - Blog"><meta name="description" content="This is all the blog posts that Rohit Pai has posted. You'll find posts on various topics, mostly on tech but some on various other random topics."><meta name="keywords" content="Blog, all posts, rohit, pai, rohit pai, tech, web development, self-hosting, hosting"><meta name="robots" content="index, follow"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="language" content="English"><meta name="author" content="Rohit Pai"><link rel="stylesheet" href="/blog/css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script><script type="text/javascript" src="https://platform-api.sharethis.com/js/sharethis.js#property=6550cdc47a115e0012964576&product=sop" async="async"></script></head><body><nav><input type="checkbox" id="nav-check"><h1><a href="/" class="link">rohit pai</a></h1><div class="nav-btn"><label for="nav-check"><span></span> <span></span> <span></span></label></div><ul><li><a href="/#about" class="textShadow link">about</a></li><li><a href="/#curriculumVitae" class="textShadow link">cv</a></li><li><a href="/#projects" class="textShadow link">projects</a></li><li><a href="/#contact" class="textShadow link">contact</a></li><li><a href="/blog" class="textShadow link active">blog</a></li></ul></nav><header><div><h1>full stack developer</h1><a href="/#sayHello" class="btn btnPrimary boxShadowIn boxShadowOut">Contact Me</a> <a href="" id="arrow"><i class="fa-solid fa-chevron-down"></i></a></div></header><div class="menuBar"><div class="menu"><ul><li><a href="/blog" class="link active">All posts</a></li><li><a href="/blog/category" class="link">categories</a></li><li><label for="searchField" aria-hidden="true" hidden>search</label> <input type="search" name="search" id="searchField" placeholder="Search..."> <button type="submit" id="searchBtn" class="btn btnPrimary"><i class="fa fa-search"></i></button></li></ul></div></div><main id="main"></main><div class="modal-container" id="cookiePopup"><div class="modal"><div class="modal-content"><h2><i class="fas fa-cookie-bite"></i> Hey I use cookies btw</h2><!-- cookie info text and 2 links disagree and agree, clicking disagree should redirect to google.co.uk --><p>Just to let you know, I use cookies to give you the best experience on my blog. By clicking agree I'll assume that you are happy with it. <a href="/blog/policy/cookie" class="link">Read more</a></p><div class="flexRow"><button class="btn btnPrimary" id="cookieAccept">agree</button> <a href="https://google.co.uk" class="btn btnPrimary">disagree</a></div></div></div></div><footer class="flexRow"><div class="nav"><ul><li><a href="/blog/policy/privacy" class="link">privacy policy</a></li><li><a href="/blog/policy/cookie" class="link">cookie policy</a></li></ul></div><p>© <span id="year"></span> Rohit Pai all rights reserved</p><div class="button"><button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button></div></footer><script src="/js/typewriter.js"></script><script src="/blog/js/index.js"></script><script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script></body></html>
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function switchView(e,t){document.querySelector(e).classList.toggle("shown"),setTimeout((()=>document.querySelector(e).style.transform="translateX(150vw)"),500),setTimeout((()=>document.querySelector(e).style.display="none"),500),setTimeout((()=>document.querySelector(t).style.removeProperty("display")),200),setTimeout((()=>document.querySelector(t).classList.toggle("shown")),300),setTimeout((()=>document.querySelector(t).style.removeProperty("transform")),400)}document.addEventListener("DOMContentLoaded",(e=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok&&(window.location.href="./editor.html")}))})),document.querySelector("#login form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;if(e.target.username.value.length>0&&e.target.password.value.length>0)return t.append("username",e.target.username.value),t.append("password",e.target.password.value),void fetch("/api/user/login",{method:"POST",body:t}).then((e=>e.json().then((t=>{if(e.ok)return localStorage.setItem("token",t.token),void(window.location.href="./editor.html");400!==e.status?showErrorMessage("Invalid username or password.","login"):showErrorMessage("Please type in a username and password.","login")}))));showErrorMessage("Please type in a username and password.","login")})),document.querySelector("#loginError .close").addEventListener("click",(()=>document.querySelector("#loginError").classList.toggle("hidden"))),document.querySelector("#resetError .close").addEventListener("click",(()=>document.querySelector("#resetError").classList.toggle("hidden"))),document.querySelector("#changeError .close").addEventListener("click",(()=>document.querySelector("#changeError").classList.toggle("hidden"))),document.querySelectorAll("form i.fa-eye").forEach((e=>{e.addEventListener("click",(e=>{if("password"===e.target.previousElementSibling.type)return e.target.previousElementSibling.type="text",e.target.classList.remove("fa-eye"),void e.target.classList.add("fa-eye-slash");e.target.previousElementSibling.type="password",e.target.classList.remove("fa-eye-slash"),e.target.classList.add("fa-eye")}))})),document.querySelector("#resetPwd").addEventListener("click",(()=>{switchView("#login","#resetPassword")})),document.querySelector("#loginBtn").addEventListener("click",(()=>{switchView("#resetPassword","#login")})),document.querySelector("#resetPassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.email?(window.email=e.target.email.value,t.append("email",e.target.email.value),fetch(`/api/user/checkResetEmail/${e.target.email.value}`).then((e=>{e.ok&&switchView("#resetPassword","#checkResetCode"),showErrorMessage("Invalid email.","reset")}))):showErrorMessage("Please type in your email.","reset")})),document.querySelector("#checkResetCode form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.code.value?(t.append("code",e.target.code.value),fetch(`/api/user/checkResetCode/${e.target.code.value}`).then((e=>{e.ok&&switchView("#checkResetCode","#changePassword"),showErrorMessage("Invalid code.","resetCode")}))):showErrorMessage("Please type in your reset code.","check")})),document.querySelector("#changePassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.pass.value||""!==e.target.rePass.value?e.target.pass.value===e.target.rePass.value?(t.append("password",e.target.pass.value),fetch("/api/user/changePassword",{method:"POST",body:t}).then((e=>{e.ok&&switchView("#changePassword","#login"),showErrorMessage("Something went wrong.","change")}))):showErrorMessage("Passwords do not match.","change"):showErrorMessage("Please type in a new password.","change")}));
|
||||
function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function switchView(e,t){document.querySelector(e).classList.toggle("shown"),setTimeout((()=>document.querySelector(e).style.transform="translateX(150vw)"),500),setTimeout((()=>document.querySelector(e).style.display="none"),500),setTimeout((()=>document.querySelector(t).style.removeProperty("display")),200),setTimeout((()=>document.querySelector(t).classList.toggle("shown")),300),setTimeout((()=>document.querySelector(t).style.removeProperty("transform")),400)}document.addEventListener("DOMContentLoaded",(e=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok&&(window.location.href="./editor.html")}))})),document.querySelector("#login form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;if(e.target.username.value.length>0&&e.target.password.value.length>0)return t.append("username",e.target.username.value),t.append("password",e.target.password.value),void fetch("/api/user/login",{method:"POST",body:t}).then((e=>e.json().then((t=>{if(e.ok)return localStorage.setItem("token",t.token),void(window.location.href="./editor.html");400!==e.status?401!==e.status?showErrorMessage(t.error,"login"):showErrorMessage("Invalid username or password.","login"):showErrorMessage("Please type in a username and password.","login")}))));showErrorMessage("Please type in a username and password.","login")})),document.querySelector("#loginError .close").addEventListener("click",(()=>document.querySelector("#loginError").classList.toggle("hidden"))),document.querySelector("#resetError .close").addEventListener("click",(()=>document.querySelector("#resetError").classList.toggle("hidden"))),document.querySelector("#changeError .close").addEventListener("click",(()=>document.querySelector("#changeError").classList.toggle("hidden"))),document.querySelectorAll("form i.fa-eye").forEach((e=>{e.addEventListener("click",(e=>{if("password"===e.target.previousElementSibling.type)return e.target.previousElementSibling.type="text",e.target.classList.remove("fa-eye"),void e.target.classList.add("fa-eye-slash");e.target.previousElementSibling.type="password",e.target.classList.remove("fa-eye-slash"),e.target.classList.add("fa-eye")}))})),document.querySelector("#resetPwd").addEventListener("click",(()=>{switchView("#login","#resetPassword")})),document.querySelector("#loginBtn").addEventListener("click",(()=>{switchView("#resetPassword","#login")})),document.querySelector("#resetPassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.email?(window.email=e.target.email.value,t.append("email",e.target.email.value),fetch(`/api/user/checkResetEmail/${e.target.email.value}`).then((e=>{e.ok&&switchView("#resetPassword","#checkResetCode"),showErrorMessage("Invalid email.","reset")}))):showErrorMessage("Please type in your email.","reset")})),document.querySelector("#checkResetCode form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.code.value?(t.append("code",e.target.code.value),fetch(`/api/user/checkResetCode/${e.target.code.value}`).then((e=>{e.ok&&switchView("#checkResetCode","#changePassword"),showErrorMessage("Invalid code.","resetCode")}))):showErrorMessage("Please type in your reset code.","check")})),document.querySelector("#changePassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.pass.value||""!==e.target.rePass.value?e.target.pass.value===e.target.rePass.value?(t.append("password",e.target.pass.value),fetch("/api/user/changePassword",{method:"POST",body:t}).then((e=>{e.ok&&switchView("#changePassword","#login"),showErrorMessage("Something went wrong.","change")}))):showErrorMessage("Passwords do not match.","change"):showErrorMessage("Please type in a new password.","change")}));
|
||||
Reference in New Issue
Block a user