aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Controller/FeedController.php47
-rw-r--r--src/Controller/RouteController.php171
-rw-r--r--src/Model/ApiService.php218
-rw-r--r--src/Model/CacheService.php10
-rw-r--r--src/View/BaseTemplate.php35
-rw-r--r--src/View/class-template.php58
6 files changed, 481 insertions, 58 deletions
diff --git a/src/Controller/FeedController.php b/src/Controller/FeedController.php
new file mode 100644
index 0000000..0e3e3b4
--- /dev/null
+++ b/src/Controller/FeedController.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace HN\Controllers;
+
+class FeedController
+{
+ /**
+ * @var string
+ */
+ private string $canonical_url;
+ /**
+ * @var string
+ */
+ private string $description;
+ /**
+ * @var string
+ */
+ private string $title;
+ /**
+ * @var string
+ */
+ private string $content;
+ /**
+ * @var false|string
+ */
+ private mixed $current_year;
+
+ public function __construct(string $canonical_url, string $description, string $title, string $content)
+ {
+ $this->canonical_url = $canonical_url;
+ $this->description = $description;
+ $this->title = $title;
+ $this->content = $content;
+ $this->current_year = date("Y");
+ }
+
+ /**
+ * Request template to be presented to the user
+ *
+ * @access public
+ * @author cmc <hello@cleberg.net>
+ */
+ public function render(): void
+ {
+ include_once 'src/View/BaseTemplate.php';
+ }
+}
diff --git a/src/Controller/RouteController.php b/src/Controller/RouteController.php
new file mode 100644
index 0000000..0d9befa
--- /dev/null
+++ b/src/Controller/RouteController.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace HN\Controllers;
+
+require_once 'src/Controller/FeedController.php';
+
+use function HN\Models\GetApiResults;
+use function HN\Models\ParseItem;
+use function HN\Models\ParseStories;
+use function HN\Models\ParseUser;
+
+class RouteController
+{
+ /**
+ * @var string
+ */
+ private string $request;
+
+ public function __construct(string $request)
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * Route the user to the appropriate function, based on the URL
+ *
+ * @access public
+ * @return void No return type; send user to FeedController->render() or a 404 error
+ * @author cmc <hello@cleberg.net>
+ */
+ public function routeUser(): void
+ {
+ include_once 'src/Model/ApiService.php';
+ $path = ltrim($this->request, '/');
+ $elements = explode('/', $path);
+
+ switch (array_shift($elements)) {
+ case '':
+ case 'top':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'],
+ 'The top stories from Hacker News, proxied by hn.',
+ 'hn',
+ ParseStories(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/topstories.json?limitToFirst=10&orderBy="$key"'
+ ),
+ 'Top'
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'best':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/Best/',
+ 'The best stories from Hacker News, proxied by hn.',
+ 'hn ~ best',
+ ParseStories(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/beststories.json?limitToFirst=10&orderBy="$key"'
+ ),
+ 'Best'
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'new':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/new/',
+ 'The newest stories from Hacker News, proxied by hn.',
+ 'hn ~ new',
+ ParseStories(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/newstories.json?limitToFirst=10&orderBy="$key"'
+ ),
+ 'New'
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'ask':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/ask/',
+ 'The top asks from Hacker News, proxied by hn.',
+ 'hn ~ ask',
+ ParseStories(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/askstories.json?limitToFirst=10&orderBy="$key"'
+ ),
+ 'Ask'
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'show':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/show/',
+ 'The latest showcases from Hacker News, proxied by hn.',
+ 'hn ~ show',
+ ParseStories(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/showstories.json?limitToFirst=10&orderBy="$key"'
+ ),
+ 'Show'
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'job':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/job/',
+ 'The latest jobs from Hacker News, proxied by hn.',
+ 'hn ~ jobs',
+ ParseStories(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/jobstories.json?limitToFirst=10&orderBy="$key"'
+ ),
+ 'Job'
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'user':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/user/' . $elements[0],
+ 'The Hacker News profile for ' . $elements[0] . ', proxied by hn.',
+ 'hn ~ ' . $elements[0],
+ ParseUser(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/user/' . $elements[0] . '.json'
+ ),
+ 'User: ' . $elements[0]
+ )
+ );
+
+ $feed->render();
+ break;
+
+ case 'item':
+ $feed = new FeedController(
+ $GLOBALS['full_domain'] . '/item/' . $elements[0],
+ 'Hacker News story ' . $elements[0] . ', proxied by hn.',
+ 'hn ~ ' . $elements[0],
+ ParseItem(
+ GetApiResults(
+ 'https://hacker-news.firebaseio.com/v0/item/' . $elements[0] . '.json'
+ ),
+ 'Item: ' . $elements[0]
+ )
+ );
+
+ $feed->render();
+ break;
+
+ default:
+ header('HTTP/1.1 404 Not Found');
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Model/ApiService.php b/src/Model/ApiService.php
new file mode 100644
index 0000000..5535c12
--- /dev/null
+++ b/src/Model/ApiService.php
@@ -0,0 +1,218 @@
+<?php
+
+namespace HN\Models;
+
+/**
+ * Extract a set of stories from the Hacker News API
+ *
+ * @access public
+ * @param string $api_url The API endpoint to use for extraction
+ * @return mixed The API results formatted into an HTML section
+ * @author cmc <hello@cleberg.net>
+ */
+function GetApiResults(string $api_url): mixed
+{
+ $response = file_get_contents($api_url);
+ return json_decode($response, true);
+}
+
+/**
+ * Formats a given set of API results into an HTML section
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @param string $inline_title The <h1> title to use in the HTML
+ * @return string $html_output The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ParseStories(mixed $api_results, string $inline_title): string
+{
+ if ($api_results == "null") {
+ return '<p>ERROR: Stories not found. API returned `null`.</p>';
+ } else {
+ $html_output = '<h1>' . $inline_title . '</h1>';
+ for ($i = 0; $i < count($api_results); $i++) {
+ $story_api_results = GetApiResults('https://hacker-news.firebaseio.com/v0/item/' . $api_results[$i] . '.json');
+ $html_output .= ConstructStory($story_api_results);
+ }
+
+ return $html_output;
+ }
+}
+
+/**
+ *Extract a user's profile from Hacker News API and format in HTML
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @param string $inline_title The <h1> title to use in the HTML
+ * @return string $html_output The formatted HTML result of stories from the API
+ * @author cmc <hello@cleberg.net>
+ */
+function ParseUser(mixed $api_results, string $inline_title): string
+{
+ if ($api_results == "null") {
+ return '<p>ERROR: User not found.</p>';
+ } else {
+ $about = $api_results['about'];
+ $karma = $api_results['karma'];
+ $created = date('Y-m-d h:m:s', $api_results['created']);
+
+ $html_output = <<<EOT
+ <div class="user-details">
+ <h1>$inline_title</h1>
+ <p>About: $about</p>
+ <p>Karma: $karma</p>
+ <p>Created: <time datetime="$created">$created</time></p>
+ <br>
+ <h2>Recently Submitted</h2>
+ </div>
+ EOT;
+
+ $limit = (count($api_results['submitted']) > 10) ? 10 : count($api_results['submitted']);
+ if (count($api_results['submitted']) > 0) {
+ for ($i = 0; $i < $limit; $i++) {
+ $user_api_results = GetApiResults('https://hacker-news.firebaseio.com/v0/item/' . $api_results['submitted'][$i] . '.json');
+ $html_output .= GetItem($user_api_results);
+ }
+ } else {
+ $html_output .= '<p>User has no submissions.</p>';
+ }
+
+ return $html_output;
+ }
+}
+
+
+/**
+ * Formats one specific item requested by the user
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @param string $inline_title The <h1> title to use in the HTML
+ * @return string $html_output The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ParseItem(mixed $api_results, string $inline_title): string
+{
+ // TODO: Need to create a page specifially for /item/ requests
+ // TODO: Use the GetItem() and Construct*() functions below, then output in it's own page - possibly with a single parent and descendat, if exist
+ return 'TODO';
+}
+
+
+/**
+ * Formats one item from the API to HTML
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @return string The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function GetItem(mixed $api_results): string
+{
+ $type = $api_results['type'];
+
+ return match ($type) {
+ 'story', 'job' => ConstructStory($api_results),
+ 'comment' => ConstructComment($api_results),
+ 'poll' => ConstructPoll($api_results),
+ 'pollopt' => ConstructPollOpt($api_results),
+ default => 'ERROR: Item type not found.',
+ };
+}
+
+
+/**
+ * Creates a story HTML element
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @return string The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ConstructStory(mixed $api_results): string
+{
+ $id = $api_results['id'];
+ $url = $api_results['url'];
+ $title = $api_results['title'];
+ $time = date('Y-m-d h:m:s', $api_results['time']);
+ $by = $api_results['by'];
+ $score = $api_results['score'];
+ $descendants = $api_results['descendants'];
+
+ return <<<EOT
+ <div class="story">
+ <a href="$url">$title</a>
+ <p>
+ <time datetime="$time">$time</time>
+ by <a href="/user/$by/">$by</a>
+ | $score points
+ | <a href="/item/$id">$descendants comments</a>
+ </p>
+ </div>
+ EOT;
+}
+
+/**
+ * Creates a story discussion page with comments
+ *
+ * @access public
+ * @return string The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ConstructStoryDiscussion(): string
+{
+ // TODO: Create discussion page, using mostly the same details as ConstructStory - except you need to check for $api_results['text'] to see if the poster left text in addition to the link.
+ // : Also need to show comments (at least a list of top level ones to start)
+ return 'TODO';
+}
+
+/**
+ * Creates a comment HTML element
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @return string The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ConstructComment($api_results): string
+{
+ $time = date('Y-m-d h:m:s', $api_results['time']);
+ $text = $api_results['text'];
+ $parent = $api_results['parent'];
+
+ return <<<EOT
+ <div class="comment">
+ <p>$text</p>
+ <p><i>Submitted in response to: <a href="/item/$parent/">$parent</a></i></p>
+ <time datetime="$time">$time</time>
+ </div>
+ EOT;
+}
+
+/**
+ * Creates a poll HTML element
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @return string The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ConstructPoll($api_results): string
+{
+ return 'TODO';
+}
+
+/**
+ * Creates a poll-option HTML element
+ *
+ * @access public
+ * @param mixed $api_results The decoded API results
+ * @return string The formatted HTML result of stories from the API or the error message
+ * @author cmc <hello@cleberg.net>
+ */
+function ConstructPollOpt($api_results): string
+{
+ return 'TODO';
+} \ No newline at end of file
diff --git a/src/Model/CacheService.php b/src/Model/CacheService.php
new file mode 100644
index 0000000..2bf1f57
--- /dev/null
+++ b/src/Model/CacheService.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * TODO: Implement a way to cache certain content in SQLite or something similarly performative
+ *
+ * @access public
+ * @return void
+ * @author cmc <hello@cleberg.net>
+ */
+function CacheService() {} \ No newline at end of file
diff --git a/src/View/BaseTemplate.php b/src/View/BaseTemplate.php
new file mode 100644
index 0000000..17bc39e
--- /dev/null
+++ b/src/View/BaseTemplate.php
@@ -0,0 +1,35 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+ <title><?php echo $this->title; ?></title>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta http-equiv="x-ua-compatible" content="ie=edge">
+ <meta name="author" content="<?php echo $GLOBALS['author_name']; ?>">
+ <meta name="description" content="<?php echo $this->description; ?>">
+ <link rel="canonical" href="<?php echo $this->canonical_url; ?>">
+ <link rel="stylesheet" href="/static/styles.min.css">
+</head>
+
+<body>
+<main id="main">
+ <nav class="links">
+ <span><a href="/">Top</a> &middot; </span>
+ <span><a href="/best/">Best</a> &middot;</span>
+ <span><a href="/new/">New</a> &middot;</span>
+ <span><a href="/ask/">Ask</a> &middot;</span>
+ <span><a href="/show/">Show</a> &middot;</span>
+ <span><a href="/job/">Job</a></span>
+ </nav>
+ <?php echo $this->content; ?>
+</main>
+
+<footer>
+ <p><a href="https://sr.ht/~cmc/hn/">Source Code</a></p>
+ <p>Copyright &copy; 2023 - <?php echo $this->current_year; ?></p>
+</footer>
+
+</body>
+
+</html>
diff --git a/src/View/class-template.php b/src/View/class-template.php
deleted file mode 100644
index 9a3c598..0000000
--- a/src/View/class-template.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-namespace HN\View;
-
-/**
-* Template View
-*
-* @author cmc <hello@cleberg.net>
-*/
-class Template
-{
- /**
- * @var string
- */
- private $canonical_url;
- /**
- * @var string
- */
- private $description;
- /**
- * @var string
- */
- private $title;
- /**
- * @var string
- */
- private $content;
- /**
- * @var false|string
- */
- private $current_year;
-
- public function __construct(string $canonical_url, string $page_description, string $page_title, string $content_col) {
- $this->canonical_url = $canonical_url;
- $this->description = $page_description;
- $this->title = $page_title;
- $this->content = $content_col;
- $this->current_year = date("Y");
- }
-
- public function echo_template(string $template_file) {
- // Get the template file
- $page = file_get_contents($template_file);
-
- // Replace the template variables
- $page = str_replace('{page_title}', $this->title, $page);
- $page = str_replace('{page_description}', $this->description, $page);
- $page = str_replace('{canonical_url}', $this->canonical_url, $page);
- $page = str_replace('{content}', $this->content, $page);
- $page = str_replace('{current_year}', $this->current_year, $page);
-
- // Echo the filled-out template
- echo $page;
- }
-}
-
-// EOF
-