diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 4bb4f1cb4bd14a31da1fc99dff62c34b2ccc0f98..5db584e1c8f19605239fd8ad844e4ac52dfc6cd6 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -22,8 +22,8 @@ $routes->setDefaultMethod('index');
 $routes->setTranslateURIDashes(false);
 $routes->set404Override();
 $routes->setAutoRoute(false);
-$routes->addPlaceholder('podcastName', '[a-z0-9\_]{1,191}');
-$routes->addPlaceholder('episodeSlug', '[a-z0-9\-]{1,191}');
+$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,191}');
+$routes->addPlaceholder('episodeSlug', '[a-zA-Z0-9\-]{1,191}');
 
 /**
  * --------------------------------------------------------------------
diff --git a/app/Controllers/Episode.php b/app/Controllers/Episode.php
index 8be56caf8f7021214222ccf5c25f2dcd59ce9cc8..fc14e8957c0aa5dc49c57f1c0159192848e0d587 100644
--- a/app/Controllers/Episode.php
+++ b/app/Controllers/Episode.php
@@ -49,7 +49,7 @@ class Episode extends BaseController
                 'image' =>
                     'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
                 'title' => 'required',
-                'slug' => 'required',
+                'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
                 'description' => 'required',
                 'type' => 'required',
             ])
@@ -105,7 +105,7 @@ class Episode extends BaseController
                 'image' =>
                     'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
                 'title' => 'required',
-                'slug' => 'required',
+                'slug' => 'required|regex_match[[a-zA-Z0-9\-]{1,191}]',
                 'description' => 'required',
                 'type' => 'required',
             ])
@@ -164,6 +164,9 @@ class Episode extends BaseController
 
     public function view()
     {
+        // The page cache is set to a decade so it is deleted manually upon podcast update
+        $this->cachePage(DECADE);
+
         self::triggerWebpageHit($this->podcast->id);
 
         $data = [
diff --git a/app/Controllers/Podcast.php b/app/Controllers/Podcast.php
index 8e51850885a98f041e42a333a44a6ce2e16c0776..081f7b9faf474eb06644e2b181ac098818e5ecdf 100644
--- a/app/Controllers/Podcast.php
+++ b/app/Controllers/Podcast.php
@@ -18,7 +18,12 @@ class Podcast extends BaseController
     {
         if (count($params) > 0) {
             $podcast_model = new PodcastModel();
-            $this->podcast = $podcast_model->where('name', $params[0])->first();
+            if (
+                !($podcast = $podcast_model->where('name', $params[0])->first())
+            ) {
+                throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+            }
+            $this->podcast = $podcast;
         }
 
         return $this->$method();
@@ -32,7 +37,7 @@ class Podcast extends BaseController
         if (
             !$this->validate([
                 'title' => 'required',
-                'name' => 'required|regex_match[^[a-z0-9\_]{1,191}$]',
+                'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
                 'description' => 'required|max_length[4000]',
                 'image' =>
                     'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
@@ -91,7 +96,7 @@ class Podcast extends BaseController
         if (
             !$this->validate([
                 'title' => 'required',
-                'name' => 'required|regex_match[^[a-z0-9\_]{1,191}$]',
+                'name' => 'required|regex_match[[a-zA-Z0-9\_]{1,191}]',
                 'description' => 'required|max_length[4000]',
                 'image' =>
                     'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
@@ -150,6 +155,9 @@ class Podcast extends BaseController
 
     public function view()
     {
+        // The page cache is set to a decade so it is deleted manually upon podcast update
+        $this->cachePage(DECADE);
+
         self::triggerWebpageHit($this->podcast->id);
 
         $data = [
diff --git a/app/Controllers/UnknownUserAgents.php b/app/Controllers/UnknownUserAgents.php
index 220daf8650c528cf240f61dbba2a3d339066f6a4..82e1a2985b3637987b758a3255a701ba8e997dfc 100644
--- a/app/Controllers/UnknownUserAgents.php
+++ b/app/Controllers/UnknownUserAgents.php
@@ -3,17 +3,10 @@ use CodeIgniter\Controller;
 
 class UnknownUserAgents extends Controller
 {
-    public function index($p_id = 0)
+    public function index($last_known_id = 0)
     {
         $model = new \App\Models\UnknownUserAgentsModel();
 
-        $data = [
-            'useragents' => $model->getUserAgents($p_id),
-        ];
-
-        $this->response->setContentType('application/json');
-        $this->response->setStatusCode(\CodeIgniter\HTTP\Response::HTTP_OK);
-
-        echo view('json/unknownuseragents', $data);
+        return $this->response->setJSON($model->getUserAgents($last_known_id));
     }
 }
diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
index 11cf98d780c2b5c2e48293d58e28a78b2904fd6b..4a4fc52894eadfb02081de085062087228b1db5e 100644
--- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php
+++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
@@ -58,13 +58,6 @@ class AddEpisodes extends Migration
                 'comment' =>
                     'An episode description. Description is text containing one or more sentences describing your episode to potential listeners. You can specify up to 4000 characters. You can use rich text formatting and some HTML (<p>, <ol>, <ul>, <li>, <a>) if wrapped in the <CDATA> tag. To include links in your description or rich HTML, adhere to the following technical guidelines: enclose all portions of your XML that contain embedded HTML in a CDATA section to prevent formatting issues, and to ensure proper link functionality.',
             ],
-            'duration' => [
-                'type' => 'INT',
-                'constraint' => 10,
-                'unsigned' => true,
-                'comment' =>
-                    'The duration of an episode. Different duration formats are accepted however it is recommended to convert the length of the episode into seconds.',
-            ],
             'image_uri' => [
                 'type' => 'VARCHAR',
                 'constraint' => 1024,
@@ -90,7 +83,7 @@ class AddEpisodes extends Migration
                 'type' => 'INT',
                 'constraint' => 10,
                 'unsigned' => true,
-                'null' => true,
+                'default' => 1,
                 'comment' =>
                     'The episode season number. If an episode is within a season use this tag. Where season is a non-zero integer (1, 2, 3, etc.) representing your season number. To allow the season feature for shows containing a single season, if only one season exists in the RSS feed, Apple Podcasts doesn’t display a season number. When you add a second season to the RSS feed, Apple Podcasts displays the season numbers.',
             ],
@@ -136,9 +129,6 @@ class AddEpisodes extends Migration
         $this->forge->addKey('id', true);
         $this->forge->addUniqueKey(['podcast_id', 'slug']);
 
-        // FIXME: as season_number can be null, the unique key constraint is useless when it is
-        // the majority of RDBMS behave that way
-        // possible solution: remove the null constraint on the season_number and set a default
         $this->forge->addUniqueKey(['podcast_id', 'season_number', 'number']);
         $this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
         $this->forge->createTable('episodes');
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index 84f2388af36313c35375665fa0e7d20d2a5cb83f..bd29dbf608c0f4a2ac26daac7974c1259d9f085d 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -39,14 +39,8 @@ class EpisodeModel extends Model
     protected $useSoftDeletes = true;
     protected $useTimestamps = true;
 
-    protected $afterInsert = [
-        'writeEnclosureMetadata',
-        'clearPodcastFeedCache',
-    ];
-    protected $afterUpdate = [
-        'writeEnclosureMetadata',
-        'clearPodcastFeedCache',
-    ];
+    protected $afterInsert = ['writeEnclosureMetadata', 'clearCache'];
+    protected $afterUpdate = ['writeEnclosureMetadata', 'clearCache'];
 
     protected function writeEnclosureMetadata(array $data)
     {
@@ -61,7 +55,7 @@ class EpisodeModel extends Model
         return $data;
     }
 
-    protected function clearPodcastFeedCache(array $data)
+    protected function clearCache(array $data)
     {
         $episode = $this->find(
             is_array($data['id']) ? $data['id'][0] : $data['id']
@@ -69,6 +63,9 @@ class EpisodeModel extends Model
 
         $cache = \Config\Services::cache();
 
+        // delete cache for rss feed, podcast and episode pages
         $cache->delete(md5($episode->podcast->feed_url));
+        $cache->delete(md5($episode->podcast->link));
+        $cache->delete(md5($episode->link));
     }
 }
diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php
index 36d377041049b8d07f021c800c44ecabbc01cc57..fba25f5ce756da2fbaf9c793348cbaf6f182e477 100644
--- a/app/Models/PodcastModel.php
+++ b/app/Models/PodcastModel.php
@@ -40,10 +40,10 @@ class PodcastModel extends Model
 
     protected $useTimestamps = true;
 
-    protected $afterInsert = ['clearPodcastFeedCache'];
-    protected $afterUpdate = ['clearPodcastFeedCache'];
+    protected $afterInsert = ['clearCache'];
+    protected $afterUpdate = ['clearCache'];
 
-    protected function clearPodcastFeedCache(array $data)
+    protected function clearCache(array $data)
     {
         $podcast = $this->find(
             is_array($data['id']) ? $data['id'][0] : $data['id']
@@ -51,6 +51,12 @@ class PodcastModel extends Model
 
         $cache = \Config\Services::cache();
 
+        // delete cache for rss feed and podcast pages
         $cache->delete(md5($podcast->feed_url));
+        $cache->delete(md5($podcast->link));
+        // TODO: clear cache for every podcast's episode page?
+        // foreach ($podcast->episodes as $episode) {
+        //     $cache->delete(md5($episode->link));
+        // }
     }
 }
diff --git a/app/Models/UnknownUserAgentsModel.php b/app/Models/UnknownUserAgentsModel.php
index ba6e69ef10a807d32b2a2eda6909494a56e545dd..5cd0b71b169e4ecd2ee19c694b3ae9d61fdad023 100644
--- a/app/Models/UnknownUserAgentsModel.php
+++ b/app/Models/UnknownUserAgentsModel.php
@@ -16,8 +16,8 @@ class UnknownUserAgentsModel extends Model
 
     protected $allowedFields = [];
 
-    public function getUserAgents($p_id = 0)
+    public function getUserAgents($last_known_id = 0)
     {
-        return $this->where('id>', $p_id)->findAll();
+        return $this->where('id>', $last_known_id)->findAll();
     }
 }
diff --git a/app/Views/json/unknownuseragents.php b/app/Views/json/unknownuseragents.php
deleted file mode 100644
index 9014c0df08d1a2bc1f03a1b7da2a095d4ac7727d..0000000000000000000000000000000000000000
--- a/app/Views/json/unknownuseragents.php
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-if (!empty($useragents) && is_array($useragents)) {
-    echo json_encode($useragents);
-}
-?>