Created feeds and UI for feeds and newsletter
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s

Signed-off-by: rodude123 <rodude123@gmail.com>
This commit is contained in:
2023-11-14 01:02:27 +00:00
parent 6cfea3fc98
commit f27a5113b1
32 changed files with 2729 additions and 671 deletions
+132 -12
View File
@@ -2,13 +2,18 @@
namespace api\blog;
use api\utils\feedGenerator\FeedWriter;
use api\utils\imgUtils;
use DOMDocument;
use PDO;
use Psr\Http\Message\UploadedFileInterface;
use function DI\string;
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";
/**
* Blog Data Class
@@ -18,12 +23,15 @@ class blogData
{
/**
* Get all blog posts
* @return array - Array of all blog posts or error message
* @return array<array> - Array of all blog posts or error message
*/
public function getBlogPosts(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY featured DESC, dateCreated DESC;");
$stmt = $conn->prepare("SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT(dateModified, '%Y-%m-%dT%TZ') AS dateModified, featured, abstract,
headerImg, body, bodyText, categories, folderID FROM blog ORDER BY featured DESC,
dateCreated DESC;");
$stmt->execute();
// set the resulting array to associative
@@ -40,12 +48,15 @@ class blogData
/**
* Get a blog post with the given ID
* @param string $title - Title of the blog post
* @return array - Array of all blog posts or error message
* @return array - Array of blog post or error message
*/
public function getBlogPost(string $title): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog WHERE title = :title;");
$stmt = $conn->prepare("SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT(dateModified, '%Y-%m-%dT%TZ') AS dateModified, featured, abstract,
headerImg, body, bodyText, categories, folderID FROM blog WHERE
title = :title;");
$stmt->bindParam(":title", $title);
$stmt->execute();
@@ -67,7 +78,10 @@ class blogData
public function getLatestBlogPost(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY dateCreated DESC LIMIT 1;");
$stmt = $conn->prepare("SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT(dateModified, '%Y-%m-%dT%TZ') AS dateModified, featured, abstract,
headerImg, body, bodyText, categories, folderID FROM blog ORDER BY
dateCreated DESC LIMIT 1;");
$stmt->execute();
// set the resulting array to associative
@@ -88,7 +102,9 @@ class blogData
public function getFeaturedBlogPost(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog WHERE featured = 1;");
$stmt = $conn->prepare("SELECT ID, title, DATE_FORMAT(dateCreated, '%Y-%m-%dT%TZ') AS dateCreated,
DATE_FORMAT(dateModified, '%Y-%m-%dT%TZ') AS dateModified, featured, abstract,
headerImg, body, bodyText, categories, folderID FROM blog WHERE featured = 1;");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
@@ -399,11 +415,10 @@ class blogData
if (!in_array($from . $file, $srcList))
{
unlink($from . $file);
continue;
}
else
{
rename($from . $file, $to . $file);
}
rename($from . $file, $to . $file);
}
}
@@ -418,7 +433,7 @@ class blogData
/**
* Get all posts with the given category
* @param string $category - Category of the post
* @return array - Array of all posts with the given category or error message
* @return array<array> - Array of all posts with the given category or error message
*/
public function getPostsByCategory(string $category): array
{
@@ -432,7 +447,7 @@ class blogData
/**
* Search for a blog post with the given search term
* @param string $searchTerm - Search term
* @return array - Array of all posts with the given search term or error message
* @return array<array> - Array of all posts with the given search term or error message
*/
public function searchBlog(string $searchTerm): array
{
@@ -527,4 +542,109 @@ class blogData
return $result;
}
/**
* Generate the XML feed
* @param mixed $type - Type of feed
* @return array|string - Error message or the XML feed
*/
private function generateXMLFeed(mixed $type): array|string
{
ob_start();
$feed = new FeedWriter($type);
$feed->setTitle("Rohit Pai's Blog");
$feed->setLink('https://rohitpai.co.uk/blog');
$feed->setFeedURL('https://rohitpai.co.uk/api/blog/feed/atom');
$feed->setChannelElement('updated', date(DATE_ATOM, time()));
$feed->setChannelElement('author', ['name' => 'Rohit Pai']);
$posts = $this->getBlogPosts();
if (isset($posts["errorMessage"]))
{
return $posts;
}
foreach ($posts as $post)
{
$newItem = $feed->createNewItem();
$newItem->setTitle($post["title"]);
$newItem->setLink("https://rohitpai.co.uk/blog/post/" . rawurlencode($post["title"]) . "#disqus_thread");
$newItem->setDate($post["dateModified"]);
$newItem->setDescription($post["body"]);
$feed->addItem($newItem);
}
$feed->generateFeed();
$atom = ob_get_contents();
ob_end_clean();
return $atom;
}
/**
* Generate the JSON feed
* @return array|array[] - Error message or the JSON feed
*/
private function generateJSONFeed(): array
{
$posts = $this->getBlogPosts();
if (isset($posts["errorMessage"]))
{
return $posts;
}
$json = array();
$json["version"] = "https://jsonfeed.org/version/1.1";
$json["title"] = "Rohit Pai's Blog";
$json["home_page_url"] = "https://rohitpai.co.uk/blog";
$json["feed_url"] = "https://rohitpai.co.uk/api/blog/feed/json";
$json["description"] = "Rohit Pai's personal blog on all things self hosting and various other tech topics";
$json["author"] = array(
"name" => "Rohit Pai",
"url" => "https://rohitpai.co.uk",
"avatar" => "https://rohitpai.co.uk/imgs/profile.jpg"
);
$items = array();
foreach ($posts as $post)
{
$items[] = array(
"id" => string($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"],
"banner_image" => "https://rohitpai.co.uk/" . rawurlencode($post["headerImg"]),
"content_html" => $post["body"]
);
}
$json["items"] = $items;
return $json;
}
/**
* Generate the RSS feed based on type
* @param string $type - Type of feed
* @return string|array - RSS feed or an error message
*/
public function getFeed(string $type): string|array
{
$feed = "";
if ($type == "atom")
{
$feed = $this->generateXMLFeed(ATOM);
}
if ($type == "rss")
{
$feed = $this->generateXMLFeed(RSS2);
}
if ($type == "json")
{
$feed = $this->generateJSONFeed();
}
return $feed;
}
}
+171 -131
View File
@@ -4,6 +4,7 @@ namespace api\blog;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "blogData.php";
use api\utils\routesInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -80,35 +81,15 @@ class blogRoutes implements routesInterface
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] != null)
if ($args["type"] == null)
{
if ($args["type"] == "latest")
{
$post = $this->blogData->getLatestBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode($post));
return $response;
}
if ($args["type"] == "featured")
{
$post = $this->blogData->getFeaturedBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($post));
return $response;
}
$post = $this->blogData->getBlogPost($args["type"]);
if ($args["type"] == "latest")
{
$post = $this->blogData->getLatestBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
@@ -119,114 +100,173 @@ class blogRoutes implements routesInterface
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
if ($args["type"] == "featured")
{
$post = $this->blogData->getFeaturedBlogPost();
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($post));
return $response;
}
$post = $this->blogData->getBlogPost($args["type"]);
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
$response->getBody()->write(json_encode($post));
return $response;
});
$app->get("/blog/feed/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
$feed = $this->blogData->getFeed($args["type"]);
if (is_array($feed))
{
$response->getBody()->write(json_encode($feed));
return $response->withStatus(404);
}
if ($args["type"] == "atom")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/atom+xml");
}
if ($args["type"] == "rss")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/rss+xml");
}
if ($args["type"] == "json")
{
$response->getBody()->write(json_encode($feed));
return $response->withHeader("Content-Type", "application/feed+json");
}
$response->getBody()->write(json_encode(array("error" => "Invalid feed type")));
return $response->withStatus(400);
});
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
{
if ($args["searchTerm"] != null)
if ($args["searchTerm"] == null)
{
$posts = $this->blogData->searchBlog($args["searchTerm"]);
$json = json_encode($posts);
$response->getBody()->write($json);
if (array_key_exists("errorMessage", $posts))
{
$response->withStatus(404);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
return $response->withStatus(400);
$posts = $this->blogData->searchBlog($args["searchTerm"]);
$json = json_encode($posts);
$response->getBody()->write($json);
if (array_key_exists("errorMessage", $posts))
{
$response->withStatus(404);
}
return $response;
});
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
return $response->withStatus(400);
}
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["bodyText"], $data["dateModified"], $data["categories"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post not found")));
return $response->withStatus(404);
}
if ($message === "unset featured")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot unset featured post, try updating another post to be featured first")));
return $response->withStatus(409);
}
if (!is_bool($message) || $message === false)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => $message)));
return $response->withStatus(500);
}
$response->withStatus(201);
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
return $response->withStatus(400);
}
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["bodyText"], $data["dateModified"], $data["categories"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post not found")));
return $response->withStatus(404);
}
if ($message === "unset featured")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot unset featured post, try updating another post to be featured first")));
return $response->withStatus(409);
}
if (!is_bool($message) || $message === false)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => $message)));
return $response->withStatus(500);
}
$response->withStatus(201);
return $response;
});
$app->delete("/blog/post/{id}", function (Request $request, Response $response, $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$message = $this->blogData->deletePost($args["id"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post 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);
}
if ($message === "cannot delete")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot delete featured post")));
return $response->withStatus(409);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
$message = $this->blogData->deletePost($args["id"]);
if ($message === "post not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, post 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);
}
if ($message === "cannot delete")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Error, cannot delete featured post")));
return $response->withStatus(409);
}
return $response;
});
$app->post("/blog/post", function (Request $request, Response $response)
@@ -290,28 +330,28 @@ class blogRoutes implements routesInterface
{
$files = $request->getUploadedFiles();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "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))));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode($message));
return $response->withStatus(201);
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "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))));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode($message));
return $response->withStatus(201);
});
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ class projectData
{
/**
* Get all project data
* @return array - Array of all project data or error message
* @return array<array> - Array of all project data or error message
*/
public function getProjectData(): array
{
+62 -62
View File
@@ -50,78 +50,78 @@ class projectRoutes implements routesInterface
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
$isMainProject = $data["isMainProject"] === "true";
$update = $this->projectData->updateProjectData($args["id"], $data["title"], $isMainProject, $data["information"], $data["projectLink"], $data["gitLink"]);
if ($update === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($update === "unset main project")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
return $response->withStatus(400);
}
if (!$update)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
$isMainProject = $data["isMainProject"] === "true";
$update = $this->projectData->updateProjectData($args["id"], $data["title"], $isMainProject, $data["information"], $data["projectLink"], $data["gitLink"]);
if ($update === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($update === "unset main project")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
return $response->withStatus(400);
}
if (!$update)
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
});
$app->delete("/projectData/{id}", function (Request $request, Response $response, array $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$message = $this->projectData->deleteProjectData($args["id"]);
if ($message === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($message === "cannot delete")
{
//uh oh cannot delete the main project
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
return $response->withStatus(409);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
$message = $this->projectData->deleteProjectData($args["id"]);
if ($message === "project not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found")));
return $response->withStatus(404);
}
if ($message === "cannot delete")
{
//uh oh cannot delete the main project
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
return $response->withStatus(409);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
return $response->withStatus(500);
}
return $response;
});
$app->post("/projectData", function (Request $request, Response $response)
+1 -1
View File
@@ -14,7 +14,7 @@ class timelineData
{
/**
* Get all education data
* @return array - Array of all education data or error message
* @return array<array> - Array of all education data or error message
*/
public function getEduData(): array
{
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace api\utils\feedGenerator;
/**
* Universal Feed Writer
*
* FeedItem class - Used as a feed element in FeedWriter class
*
* @package UniversalFeedWriter
* @author Anis uddin Ahmad <anisniit@gmail.com>
* @link http://www.ajaxray.com/projects/rss
*/
class FeedItem
{
private array $elements = []; // Collection of feed elements
private string $version;
/**
* Constructor
*
* @param string $version (RSS1/RSS2/ATOM) RSS2 is the default.
*/
public function __construct(string $version = RSS2)
{
$this->version = $version;
}
/**
* Add an element to elements array
*
* @param string $elementName The tag name of an element
* @param string $content The content of the tag
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
*/
public function addElement(string $elementName, string $content, ?array $attributes = null): void
{
$this->elements[$elementName]['name'] = $elementName;
$this->elements[$elementName]['content'] = $content;
$this->elements[$elementName]['attributes'] = $attributes;
}
/**
* Set multiple feed elements from an array.
* Elements that have attributes cannot be added by this method
*
* @param array $elementArray Array of elements in 'tagName' => 'tagContent' format.
*/
public function addElementArray(array $elementArray): void
{
foreach ($elementArray as $elementName => $content)
{
$this->addElement($elementName, $content);
}
}
/**
* Return the collection of elements in this feed item
*
* @return array
*/
public function getElements(): array
{
return $this->elements;
}
// Wrapper functions ------------------------------------------------------
/**
* Set the 'description' element of the feed item
*
* @param string $description The content of the 'description' element
*/
public function setDescription(string $description): void
{
$tag = ($this->version === ATOM) ? 'summary' : 'description';
$this->addElement($tag, $description);
}
/**
* Set the 'title' element of the feed item
*
* @param string $title The content of the 'title' element
*/
public function setTitle(string $title): void
{
$this->addElement('title', $title);
}
/**
* Set the 'date' element of the feed item
*
* @param string|int $date The content of the 'date' element
*/
public function setDate(string|int $date): void
{
if (!is_numeric($date))
{
$date = strtotime($date);
}
if ($this->version === ATOM)
{
$tag = 'updated';
$value = date(DATE_ATOM, $date);
}
elseif ($this->version === RSS2)
{
$tag = 'pubDate';
$value = date(DATE_RSS, $date);
}
else
{
$tag = 'dc:date';
$value = date("Y-m-d", $date);
}
$this->addElement($tag, $value);
}
/**
* Set the 'link' element of the feed item
*
* @param string $link The content of the 'link' element
*/
public function setLink(string $link): void
{
if ($this->version === RSS2 || $this->version === RSS1)
{
$this->addElement('link', $link);
}
else
{
$this->addElement('link', '', ['href' => $link]);
$this->addElement('id', FeedWriter::uuid($link, 'urn:uuid:'));
}
}
/**
* Set the 'encloser' element of the feed item
* For RSS 2.0 only
*
* @param string $url The url attribute of the encloser tag
* @param string $length The length attribute of the encloser tag
* @param string $type The type attribute of the encloser tag
*/
public function setEncloser(string $url, string $length, string $type): void
{
$attributes = ['url' => $url, 'length' => $length, 'type' => $type];
$this->addElement('enclosure', '', $attributes);
}
}
+386
View File
@@ -0,0 +1,386 @@
<?php
namespace api\utils\feedGenerator;
require_once "FeedItem.php";
/**
* Universal Feed Writer class
*
* Generate RSS 1.0, RSS 2.0, and Atom Feed
*
* @package UniversalFeedWriter
* @link http://www.ajaxray.com/projects/rss
*/
class FeedWriter
{
private array $channels = []; // Collection of channel elements
private array $items = []; // Collection of items as objects of FeedItem class
private array $data = []; // Store some other version-wise data
private array $CDATAEncoding = []; // The tag names that need to be encoded as CDATA
private string $version;
/**
* Constructor
*
* @param string $version The version (RSS1, RSS2, ATOM).
*/
public function __construct(string $version = RSS2)
{
$this->version = $version;
// Setting default values for essential channel elements
$this->channels['title'] = $version . ' Feed';
$this->channels['link'] = 'http://www.ajaxray.com/blog';
$this->channels["feedUrl"] = "http://example.com/feed";
// Tag names to encode in CDATA
$this->CDATAEncoding = ['description', 'content:encoded', 'summary'];
}
// Public functions
/**
* Set a channel element
*
* @param string $elementName Name of the channel tag
* @param string $content Content of the channel tag
*/
public function setChannelElement(string $elementName, string|array $content): void
{
$this->channels[$elementName] = $content;
}
/**
* Generate the actual RSS/Atom file
*/
public function generateFeed(): void
{
$this->printHead();
$this->printChannels();
$this->printItems();
$this->printTail();
}
/**
* Create a new FeedItem.
*
* @return FeedItem An instance of FeedItem class
*/
public function createNewItem(): FeedItem
{
$item = new FeedItem($this->version);
return $item;
}
/**
* Add a FeedItem to the main class
*
* @param FeedItem $feedItem An instance of FeedItem class
*/
public function addItem(FeedItem $feedItem): void
{
$this->items[] = $feedItem;
}
// Wrapper functions
/**
* Set the 'title' channel element
*
* @param string $title Value of 'title' channel tag
*/
public function setTitle(string $title): void
{
$this->setChannelElement('title', $title);
}
/**
* Set the 'description' channel element
*
* @param string $description Value of 'description' channel tag
*/
public function setDescription(string $description): void
{
$this->setChannelElement('description', $description);
}
/**
* Set the 'link' channel element
*
* @param string $link Value of 'link' channel tag
*/
public function setLink(string $link): void
{
$this->setChannelElement('link', $link);
}
/**
* Set the 'image' channel element
*
* @param string $title Title of the image
* @param string $link Link URL of the image
* @param string $url Path URL of the image
*/
public function setImage(string $title, string $link, string $url): void
{
$this->setChannelElement('image', ['title' => $title, 'link' => $link, 'url' => $url]);
}
/**
* Set the 'about' channel element. Only for RSS 1.0
*
* @param string $url Value of 'about' channel tag
*/
public function setChannelAbout(string $url): void
{
$this->data['ChannelAbout'] = $url;
}
// Other functions
/**
* Generates a UUID
*
* @param string $key An optional prefix
* @param string $prefix A prefix
* @return string The formatted UUID
*/
public static function uuid(?string $key = null, string $prefix = ''): string
{
$key = $key ?? uniqid((string)rand());
$chars = md5($key);
$uuid = substr($chars, 0, 8) . '-';
$uuid .= substr($chars, 8, 4) . '-';
$uuid .= substr($chars, 12, 4) . '-';
$uuid .= substr($chars, 16, 4) . '-';
$uuid .= substr($chars, 20, 12);
return $prefix . $uuid;
}
// Private functions
/**
* Prints the XML and RSS namespace
*/
private function printHead(): void
{
$out = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
if ($this->version == RSS2)
{
$out .= '<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
>' . PHP_EOL;
}
elseif ($this->version == RSS1)
{
$out .= '<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
>' . PHP_EOL;
}
elseif ($this->version == ATOM)
{
$out .= '<feed xmlns="http://www.w3.org/2005/Atom">' . PHP_EOL;
}
echo $out;
}
/**
* Closes the open tags at the end of the file
*/
private function printTail(): void
{
if ($this->version == RSS2)
{
echo '</channel>' . PHP_EOL . '</rss>';
}
elseif ($this->version == RSS1)
{
echo '</rdf:RDF>';
}
elseif ($this->version == ATOM)
{
echo '</feed>';
}
}
/**
* Creates a single node in XML format
*
* @param string $tagName Name of the tag
* @param mixed $tagContent Tag value as a string or an array of nested tags in 'tagName' => 'tagValue' format
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
* @return string Formatted XML tag
*/
private function makeNode(string $tagName, $tagContent, ?array $attributes = null): string
{
$nodeText = '';
$attrText = '';
if (is_array($attributes))
{
foreach ($attributes as $key => $value)
{
$attrText .= " $key=\"$value\"";
}
}
if (is_array($tagContent) && $this->version == RSS1)
{
$attrText = ' rdf:parseType="Resource"';
}
$attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == ATOM) ? ' type="html" ' : '';
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "<{$tagName}{$attrText}><![CDATA[" : "<{$tagName}{$attrText}>";
if (is_array($tagContent))
{
foreach ($tagContent as $key => $value)
{
$nodeText .= $this->makeNode($key, $value);
}
}
else
{
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $tagContent : htmlentities($tagContent);
}
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "]]></$tagName>" : "</$tagName>";
return $nodeText . PHP_EOL;
}
/**
* Prints channel elements
*/
private function printChannels(): void
{
// Start channel tag
switch ($this->version)
{
case RSS2:
echo '<channel>' . PHP_EOL;
break;
case RSS1:
echo (isset($this->data['ChannelAbout'])) ? "<channel rdf:about=\"{$this->data['ChannelAbout']}\">" : "<channel rdf:about=\"{$this->channels['link']}\">";
break;
}
// Print items of channel
foreach ($this->channels as $key => $value)
{
if ($this->version == ATOM && $key == 'link')
{
// ATOM prints the link element as an href attribute
echo $this->makeNode($key, '', ['href' => $value]);
// Add the id for ATOM
echo $this->makeNode('id', $this->uuid($value, 'urn:uuid:'));
}
else if ($this->version == ATOM && $key == 'feedUrl')
{
echo $this->makeNode('link', '', ['rel' => 'self', 'href' => $value]);
}
else
{
echo $this->makeNode($key, $value);
}
}
// RSS 1.0 has a special tag <rdf:Seq> with channel
if ($this->version == RSS1)
{
echo "<items>" . PHP_EOL . "<rdf:Seq>" . PHP_EOL;
foreach ($this->items as $item)
{
$thisItems = $item->getElements();
echo "<rdf:li resource=\"{$thisItems['link']['content']}\"/>" . PHP_EOL;
}
echo "</rdf:Seq>" . PHP_EOL . "</items>" . PHP_EOL . "</channel>" . PHP_EOL;
}
}
/**
* Prints formatted feed items
*/
private function printItems(): void
{
foreach ($this->items as $item)
{
$thisItems = $item->getElements();
// The argument is printed as rdf:about attribute of item in RSS 1.0
echo $this->startItem($thisItems['link']['content']);
foreach ($thisItems as $feedItem)
{
echo $this->makeNode($feedItem['name'], $feedItem['content'], $feedItem['attributes']);
}
echo $this->endItem();
}
}
/**
* Makes the starting tag of items
*
* @param string|false $about The value of about tag, which is used only for RSS 1.0
*/
private function startItem($about = false): void
{
if ($this->version == RSS2)
{
echo '<item>' . PHP_EOL;
}
elseif ($this->version == RSS1)
{
if ($about)
{
echo "<item rdf:about=\"$about\">" . PHP_EOL;
}
else
{
die("link element is not set.\n It's required for RSS 1.0 to be used as the about attribute of the item");
}
}
elseif ($this->version == ATOM)
{
echo "<entry>" . PHP_EOL;
}
}
/**
* Closes the feed item tag
*/
private function endItem(): void
{
if ($this->version == RSS2 || $this->version == RSS1)
{
echo '</item>' . PHP_EOL;
}
elseif ($this->version == ATOM)
{
echo "</entry>" . PHP_EOL;
}
}
/**
* Set the Feed URL
* @param string $string - The URL of the feed
* @return void
*/
public function setFeedURL(string $string): void
{
$this->setChannelElement("feedUrl", $string);
}
}
// Define constants for RSS 1.0, RSS 2.0, and Atom
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM = 'ATOM';
+6 -1
View File
@@ -65,7 +65,12 @@ class middleware
$app->add(function ($request, $handler)
{
$response = $handler->handle($request);
return $response->withHeader("Content-Type", "application/json");
$contentType = $response->getHeaderLine("Content-Type");
if (empty($contentType) || $contentType === "text/html")
{
return $response->withHeader("Content-Type", "application/json");
}
return $response;
});
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+197
View File
@@ -0,0 +1,197 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
id="svg2"
inkscape:export-ydpi="90"
inkscape:export-filename="C:\Users\Ilia\SVG\Atom_icon.png"
viewBox="0 0 285.45 280.91"
inkscape:export-xdpi="90"
version="1.1"
inkscape:version="0.91pre3 r13670"
sodipodi:docname="_svgclean2.svg"
>
<sodipodi:namedview
id="base"
fit-margin-left="10"
inkscape:zoom="1.979899"
borderopacity="1.0"
inkscape:current-layer="layer1"
inkscape:cx="30.185218"
inkscape:cy="330.70792"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:window-maximized="0"
inkscape:snap-bbox="true"
showgrid="false"
fit-margin-right="10"
inkscape:snap-nodes="false"
units="mm"
inkscape:document-units="px"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:snap-smooth-nodes="true"
inkscape:window-width="718"
fit-margin-bottom="10"
inkscape:snap-page="false"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
pagecolor="#ffffff"
inkscape:window-height="645"
fit-margin-top="10"
/>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(-.31824 -771.33)"
>
<g
id="g8434"
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
>
<ellipse
id="path3089"
style="fill:#FFFFFF"
rx="2.0633"
ry="2.0618"
cy="1037.7"
cx="14.936"
/>
<path
id="circle3412"
style="fill:#FFFFFF"
inkscape:connector-curvature="0"
transform="translate(0,1020.4)"
d="m15.045 2.7051v3.3496a11.266 11.258 0 0 1 11.154 11.213h3.3516a14.617 14.606 0 0 0 -14.506 -14.563z"
/>
<path
id="circle3428"
style="fill:#FFFFFF"
inkscape:connector-curvature="0"
transform="translate(0,1020.4)"
d="m15.045 9.4453v2.9492a4.9226 4.919 0 0 1 4.8105 4.873h2.9492a7.8711 7.8654 0 0 0 -7.76 -7.8217z"
/>
</g
>
<g
id="g8430"
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
>
<path
id="path3302"
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
inkscape:connector-curvature="0"
transform="translate(0,1020.4)"
d="m14.865 12.807c-3.944 0.0057-7.5137 0.47474-10.127 1.2383-1.3146 0.3841-2.3876 0.844-3.1562 1.375-0.76861 0.5309-1.2617 1.1648-1.2617 1.8906 0 0.7259 0.49311 1.3617 1.2617 1.8926 0.76861 0.5309 1.8417 0.97918 3.1562 1.3633 2.6292 0.7682 6.2246 1.2402 10.197 1.2402 3.9727 0 7.57-0.47204 10.199-1.2402 1.3146-0.3841 2.3857-0.83238 3.1543-1.3633 0.71878-0.49648 1.1813-1.0881 1.2402-1.7559h-0.64453c-0.05963 0.38822-0.35991 0.79958-0.9668 1.2188-0.67801 0.4684-1.6967 0.90955-2.9688 1.2812-2.5442 0.7433-6.0921 1.209-10.014 1.209-3.9216 0-7.4675-0.46568-10.012-1.209-1.2721-0.3717-2.3005-0.81285-2.9785-1.2812-0.67801-0.4683-0.98438-0.92797-0.98438-1.3555 0-0.4274 0.30637-0.89488 0.98438-1.3633 0.67801-0.4683 1.7064-0.90965 2.9785-1.2812 2.5286-0.73883 6.0485-1.2033 9.9414-1.209v-0.65039z"
/>
<circle
id="path8416"
cy="1034.4"
cx="6.1108"
r=".75893"
style="fill:#FFFFFF"
/>
</g
>
<g
id="g8439"
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
>
<g
id="g8426"
>
<path
id="path3304"
d="m14.865 12.236c-0.72049 0.36795-1.4472 0.75012-2.1816 1.1738-3.4404 1.9848-6.3203 4.1911-8.2129 6.1699-0.94628 0.9894-1.6489 1.914-2.0488 2.7578-0.39997 0.8438-0.50968 1.639-0.14648 2.2676s1.1078 0.93201 2.0391 1.0078c0.93131 0.076 2.0893-0.06106 3.4199-0.38476 2.6613-0.6484 6.0127-2.0366 9.4531-4.0215 2.0899-1.2056 3.9354-2.4914 5.5156-3.7598h-1.0293c-1.411 1.0794-3.0286 2.1662-4.8125 3.1953-3.3962 1.9593-6.7001 3.3277-9.2754 3.9551-1.2876 0.3137-2.3914 0.43995-3.2129 0.37305-0.82153-0.067-1.3291-0.31535-1.543-0.68555-0.21387-0.3702-0.16922-0.92367 0.18359-1.668 0.35282-0.7444 1.014-1.6364 1.9297-2.5938 1.8314-1.9149 4.6702-4.0894 8.0664-6.0488 0.62532-0.36075 1.2403-0.68024 1.8555-1v-0.73828z"
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
transform="translate(0,1020.4)"
inkscape:connector-curvature="0"
/>
<circle
id="circle8418"
cy="1045.1"
cx="2.8068"
r=".75893"
style="fill:#FFFFFF"
/>
</g
>
<g
id="g8422"
>
<path
id="path3300"
d="m11.682 3.1523c-0.17928-0.012267-0.35779 0.00188-0.5332 0.048828-0.70164 0.1879-1.1876 0.82968-1.502 1.709-0.31431 0.8792-0.47091 2.0283-0.50195 3.3965-0.062086 2.7365 0.4132 6.3316 1.4414 10.166s2.4148 7.1846 3.8379 9.5234c0.71153 1.1695 1.4246 2.087 2.1367 2.6914 0.71217 0.6045 1.4527 0.91642 2.1543 0.72852 0.70164-0.1879 1.1876-0.82783 1.502-1.707 0.31431-0.8793 0.48067-2.034 0.51172-3.4023 0.05491-2.42-0.33654-5.5249-1.1328-8.8594h-0.66797c0.81117 3.3483 1.2082 6.4642 1.1543 8.8418-0.03005 1.3241-0.19344 2.4196-0.4707 3.1953-0.27726 0.7756-0.64933 1.2019-1.0625 1.3125s-0.93818-0.07442-1.5664-0.60742c-0.62822-0.5332-1.3193-1.4036-2.0078-2.5352-1.377-2.2632-2.7448-5.5645-3.7598-9.3496-1.015-3.785-1.482-7.3305-1.4219-9.9785 0.030055-1.324 0.18954-2.4314 0.4668-3.207s0.64152-1.1882 1.0547-1.2988c0.41317-0.1106 0.946 0.062403 1.5742 0.5957 0.6182 0.52469 1.2989 1.3866 1.9766 2.4922v-1.1836c-0.521-0.7474-1.043-1.3562-1.561-1.7957-0.534-0.4534-1.086-0.7406-1.623-0.7774z"
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
transform="translate(0,1020.4)"
inkscape:connector-curvature="0"
/>
<circle
id="circle8420"
cx="20.264"
style="fill:#FFFFFF"
r=".75893"
cy="1043.4"
/>
</g
>
</g
>
</g
>
<metadata
id="metadata19"
>
<rdf:RDF
>
<cc:Work
>
<dc:format
>image/svg+xml</dc:format
>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
/>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
/>
<dc:publisher
>
<cc:Agent
rdf:about="http://openclipart.org/"
>
<dc:title
>Openclipart</dc:title
>
</cc:Agent
>
</dc:publisher
>
</cc:Work
>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/"
>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
/>
</cc:License
>
</rdf:RDF
>
</metadata
>
</svg
>

After

Width:  |  Height:  |  Size: 7.6 KiB

+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<path fill="#FFFFFF"
d="M854 175q-27 0 -46 19t-19 45.5t18.5 45t45 18.5t45.5 -18.5t19 -45t-18.5 -45.5t-44.5 -19zM205 192l-34 34q-83 83 -88 154t68 144l82 82q45 46 48.5 78t-33.5 69v0q-16 19 -15.5 44.5t18.5 43.5t43.5 18.5t44.5 -15.5l1 1q25 -25 47 -32t45.5 4.5t53.5 41.5l95 96
q75 74 147.5 70t155.5 -87l33 -34l-71 -72l-18 18q-47 47 -84 47.5t-82 -44.5l-112 -112q-86 -86 -169 -17l-11 -11q35 -42 31.5 -83t-45.5 -82l-100 -101q-31 -31 -40.5 -56.5t1 -51.5t42.5 -59l17 -17zM703 326q-28 0 -46.5 19t-18.5 45.5t18.5 45.5t45 19t45.5 -19
t19 -45.5t-18.5 -45t-44.5 -19.5zM551 477q-27 0 -46 19t-19 45.5t19 45.5t45.5 19t45.5 -19t19 -45.5t-19 -45t-45 -19.5z" />
</svg>

After

Width:  |  Height:  |  Size: 1023 B

+1 -1
View File
@@ -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></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="spacer"></div><p>&copy; <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><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>&copy; <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>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long