From 7b89da6106c150708782d39ed2742fe416c41e89 Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Sat, 5 Feb 2022 16:57:20 +0000
Subject: [PATCH] feat(home): sort podcasts by recent activity + add dropdown
 menu to choose between sorting options

fixes #164
---
 app/Controllers/HomeController.php |  8 +++++-
 app/Language/en/Home.php           |  6 +++++
 app/Language/fr/Home.php           |  6 +++++
 app/Models/PodcastModel.php        | 40 ++++++++++++++++++++++++++++++
 app/Resources/icons/sort.svg       |  6 +++++
 themes/cp_app/home.php             | 27 ++++++++++++++++++--
 6 files changed, 90 insertions(+), 3 deletions(-)
 create mode 100644 app/Resources/icons/sort.svg

diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php
index aeaaff0ab7..54abb0ff8a 100644
--- a/app/Controllers/HomeController.php
+++ b/app/Controllers/HomeController.php
@@ -27,7 +27,12 @@ class HomeController extends BaseController
             return redirect()->to(rtrim(host_url(), '/') . $route);
         }
 
-        $allPodcasts = (new PodcastModel())->findAll();
+        $sortOptions = ['activity', 'created_desc', 'created_asc'];
+        $sortBy = in_array($this->request->getGet('sort'), $sortOptions, true) ? $this->request->getGet(
+            'sort'
+        ) : 'activity';
+
+        $allPodcasts = (new PodcastModel())->getAllPodcasts($sortBy);
 
         // check if there's only one podcast to redirect user to it
         if (count($allPodcasts) === 1) {
@@ -38,6 +43,7 @@ class HomeController extends BaseController
         $data = [
             'metatags' => get_home_metatags(),
             'podcasts' => $allPodcasts,
+            'sortBy' => $sortBy,
         ];
 
         return view('home', $data);
diff --git a/app/Language/en/Home.php b/app/Language/en/Home.php
index eda08142ef..26d8350231 100644
--- a/app/Language/en/Home.php
+++ b/app/Language/en/Home.php
@@ -10,5 +10,11 @@ declare(strict_types=1);
 
 return [
     'all_podcasts' => 'All podcasts',
+    'sort_by' => 'Sort by',
+    'sort_options' => [
+        'activity' => 'Recent activity',
+        'created_desc' => 'Newest first',
+        'created_asc' => 'Oldest first',
+    ],
     'no_podcast' => 'No podcast found',
 ];
diff --git a/app/Language/fr/Home.php b/app/Language/fr/Home.php
index ab847e8f41..6a0627c5f0 100644
--- a/app/Language/fr/Home.php
+++ b/app/Language/fr/Home.php
@@ -10,5 +10,11 @@ declare(strict_types=1);
 
 return [
     'all_podcasts' => 'Tous les podcasts',
+    'sort_by' => 'Trier par',
+    'sort_options' => [
+        'activity' => 'Activité récente',
+        'created_desc' => 'Le plus récent d’abord',
+        'created_asc' => 'Le plus ancien d’abord',
+    ],
     'no_podcast' => 'Aucun podcast trouvé',
 ];
diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php
index 9b639d69d4..68d1e1d10b 100644
--- a/app/Models/PodcastModel.php
+++ b/app/Models/PodcastModel.php
@@ -166,6 +166,42 @@ class PodcastModel extends Model
         return $found;
     }
 
+    /**
+     * @param 'activity'|'created_asc'|'created_desc' $orderBy
+     *
+     * @return Podcast[]
+     */
+    public function getAllPodcasts(string $orderBy = null): array
+    {
+        if ($orderBy === 'activity') {
+            $prefix = $this->db->getPrefix();
+
+            $fediverseTablePrefix = config('Fediverse')
+                ->tablesPrefix;
+            $this->select(
+                'podcasts.*, MAX(' . $prefix . $fediverseTablePrefix . 'posts.published_at' . ') as max_published_at'
+            )
+                ->join(
+                    $fediverseTablePrefix . 'posts',
+                    $fediverseTablePrefix . 'posts.actor_id = podcasts.actor_id',
+                    'left'
+                )
+                ->where(
+                    '`' . $prefix . $fediverseTablePrefix . 'posts`.`published_at` <= NOW()',
+                    null,
+                    false
+                )->orWhere($fediverseTablePrefix . 'posts.published_at', null)
+                ->groupBy('cp_podcasts.actor_id')
+                ->orderBy('max_published_at', 'DESC');
+        } elseif ($orderBy === 'created_desc') {
+            $this->orderBy('created_at', 'DESC');
+        } elseif ($orderBy === 'created_asc') {
+            $this->orderBy('created_at', 'ASC');
+        }
+
+        return $this->findAll();
+    }
+
     /**
      * Gets all the podcasts a given user is contributing to
      *
@@ -378,6 +414,10 @@ class PodcastModel extends Model
     {
         $podcast = (new self())->getPodcastById(is_array($data['id']) ? $data['id'][0] : $data['id']);
 
+        // delete cache for users' podcasts
+        cache()
+            ->deleteMatching('user*podcasts');
+
         if ($podcast !== null) {
             // delete cache all podcast pages
             cache()
diff --git a/app/Resources/icons/sort.svg b/app/Resources/icons/sort.svg
new file mode 100644
index 0000000000..51df35457d
--- /dev/null
+++ b/app/Resources/icons/sort.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+  <g>
+    <path d="M0 0H24V24H0Z" fill="none"/>
+    <path d="M3 18H7V16H3ZM3 6V8H21V6Zm0 7H15V11H3Z"/>
+  </g>
+</svg>
diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php
index 39abc5cdc6..b9c1d5422e 100644
--- a/themes/cp_app/home.php
+++ b/themes/cp_app/home.php
@@ -47,10 +47,33 @@
         </h1>
     </header>
     <main class="container flex-1 px-4 py-10 mx-auto">
-        <Heading tagName="h2" class="inline-block mb-2"><?= lang('Home.all_podcasts') ?> (<?= count(
+        <div class="flex flex-wrap items-center justify-between py-2 border-b border-subtle gap-x-4">
+            <Heading tagName="h2" class="inline-block"><?= lang('Home.all_podcasts') ?> (<?= count(
             $podcasts,
         ) ?>)</Heading>
-        <div class="grid gap-4 grid-cols-cards">
+            <button class="inline-flex items-center px-2 py-1 text-sm font-semibold focus:ring-accent" id="sortby-dropdown" data-dropdown="button" data-dropdown-target="sortby-dropdown-menu" aria-haspopup="true" aria-expanded="false"><?= icon('sort', 'mr-1 text-xl opacity-50') . lang('Home.sort_by') ?></button>
+            <DropdownMenu id="sortby-dropdown-menu" labelledby="sortby-dropdown" items="<?= esc(json_encode([
+                [
+                    'type' => 'link',
+                    'title' => ($sortBy === 'activity' ? '✓ ' : '') . lang('Home.sort_options.activity'),
+                    'uri' => route_to('home') . '?sort=activity',
+                    'class' => $sortBy === 'activity' ? 'font-semibold' : '',
+                ],
+                [
+                    'type' => 'link',
+                    'title' => ($sortBy === 'created_desc' ? '✓ ' : '') . lang('Home.sort_options.created_desc'),
+                    'uri' => route_to('home') . '?sort=created_desc',
+                    'class' => $sortBy === 'created_desc' ? 'font-semibold' : '',
+                ],
+                [
+                    'type' => 'link',
+                    'title' => ($sortBy === 'created_asc' ? '✓ ' : '') . lang('Home.sort_options.created_asc'),
+                    'uri' => route_to('home') . '?sort=created_asc',
+                    'class' => $sortBy === 'created_asc' ? 'font-semibold' : '',
+                ],
+            ])) ?>" />
+        </div>
+        <div class="grid gap-4 mt-4 grid-cols-cards">
             <?php if ($podcasts): ?>
                 <?php foreach ($podcasts as $podcast): ?>
                     <a href="<?= $podcast->link ?>" class="relative w-full h-full overflow-hidden transition shadow focus:ring-accent rounded-xl border-subtle hover:shadow-xl focus:shadow-xl group border-3">
-- 
GitLab