diff --git a/app/Config/View.php b/app/Config/View.php
index 104cdd8d86795769dd1568d1ad0d0441036736ae..ae4d7475642e89ed4d661b3d8e8702df6889cc94 100644
--- a/app/Config/View.php
+++ b/app/Config/View.php
@@ -27,7 +27,8 @@ class View extends BaseView
      *
      * Examples: { title|esc(js) } { created_on|date(Y-m-d)|esc(attr) }
      *
-     * @var string[]
+     * @var array<string, string>
+     * @phpstan-var array<string, callable-string>
      */
     public $filters = [];
 
@@ -35,7 +36,8 @@ class View extends BaseView
      * Parser Plugins provide a way to extend the functionality provided by the core Parser by creating aliases that
      * will be replaced with any callable. Can be single or tag pair.
      *
-     * @var string[]
+     * @var array<string, string>
+     * @phpstan-var array<string, callable-string>
      */
     public $plugins = [];
 
diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php
index 71f7cc924640a2675134d5a212eb53ad94e7fcea..46a4f860a529ab160d615c483319bec0aceb68a5 100644
--- a/app/Controllers/EpisodeController.php
+++ b/app/Controllers/EpisodeController.php
@@ -290,7 +290,7 @@ class EpisodeController extends BaseController
         $episodeComments = model(PostModel::class)
             ->whereIn('in_reply_to_id', function (BaseBuilder $builder): BaseBuilder {
                 return $builder->select('id')
-                    ->from(config('Fediverse')->tablesPrefix . 'posts')
+                    ->from('fediverse_posts')
                     ->where('episode_id', $this->episode->id);
             })
             ->where('`published_at` <= UTC_TIMESTAMP()', null, false)
diff --git a/app/Database/Migrations/2021-05-30-101500_add_podcasts.php b/app/Database/Migrations/2021-05-30-101500_add_podcasts.php
index 006d7224a860cdb1dae3d244b60416a571fd79a9..fbf6916b4dbb90b0d4bc7af3a63e401683c7d465 100644
--- a/app/Database/Migrations/2021-05-30-101500_add_podcasts.php
+++ b/app/Database/Migrations/2021-05-30-101500_add_podcasts.php
@@ -195,7 +195,7 @@ class AddPodcasts extends BaseMigration
         $this->forge->addUniqueKey('handle');
         $this->forge->addUniqueKey('guid');
         $this->forge->addUniqueKey('actor_id');
-        $this->forge->addForeignKey('actor_id', config('Fediverse')->tablesPrefix . 'actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
         $this->forge->addForeignKey('cover_id', 'media', 'id');
         $this->forge->addForeignKey('banner_id', 'media', 'id', '', 'SET NULL');
         $this->forge->addForeignKey('category_id', 'categories', 'id');
diff --git a/app/Database/Migrations/2021-06-05-170000_add_episodes.php b/app/Database/Migrations/2021-06-05-170000_add_episodes.php
index 22641eab79ffd70f78d17afb54dace04149e070d..9aa57ba8c844353355559591526259c3c5768921 100644
--- a/app/Database/Migrations/2021-06-05-170000_add_episodes.php
+++ b/app/Database/Migrations/2021-06-05-170000_add_episodes.php
@@ -164,10 +164,10 @@ class AddEpisodes extends BaseMigration
 
         // Add Full-Text Search index on title and description_markdown
         $prefix = $this->db->getPrefix();
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
             ALTER TABLE {$prefix}episodes
-            ADD FULLTEXT(title, description_markdown);
-        CODE_SAMPLE;
+            ADD FULLTEXT title (title, description_markdown);
+        SQL;
         $this->db->query($createQuery);
     }
 
diff --git a/app/Database/Migrations/2021-08-12-150000_add_episode_comments.php b/app/Database/Migrations/2021-08-12-150000_add_episode_comments.php
index 4c1869ceb3c3ac77dccb41c4e9daf792b2701827..54202baa6f0eb3b7c7d303f93f1ad67491d8b2ea 100644
--- a/app/Database/Migrations/2021-08-12-150000_add_episode_comments.php
+++ b/app/Database/Migrations/2021-08-12-150000_add_episode_comments.php
@@ -64,12 +64,9 @@ class AddEpisodeComments extends BaseMigration
             ],
         ]);
 
-        $fediverseTablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addPrimaryKey('id');
         $this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
         $this->forge->addForeignKey('created_by', 'users', 'id');
         $this->forge->createTable('episode_comments');
     }
diff --git a/app/Database/Migrations/2021-08-12-160000_add_likes.php b/app/Database/Migrations/2021-08-12-160000_add_likes.php
index 830de1f19e47161d4f4edf2b7041c9b683d34bd8..53917ac0c49d97b391b93f92378a0645b6dfd5d0 100644
--- a/app/Database/Migrations/2021-08-12-160000_add_likes.php
+++ b/app/Database/Migrations/2021-08-12-160000_add_likes.php
@@ -27,12 +27,9 @@ class AddLikes extends BaseMigration
             ],
         ]);
 
-        $fediverseTablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
         $this->forge->addPrimaryKey(['actor_id', 'comment_id']);
-        $this->forge->addForeignKey('actor_id', $fediverseTablesPrefix . 'actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
         $this->forge->addForeignKey('comment_id', 'episode_comments', 'id', '', 'CASCADE');
         $this->forge->createTable('likes');
     }
diff --git a/app/Database/Migrations/2021-12-25-150000_add_credits_view.php b/app/Database/Migrations/2021-12-25-150000_add_credits_view.php
index bef51fa5e7fe4b7c08332098f31172854c0035f6..9955e3ca245ca907dda7db3dd12b12318d354432 100644
--- a/app/Database/Migrations/2021-12-25-150000_add_credits_view.php
+++ b/app/Database/Migrations/2021-12-25-150000_add_credits_view.php
@@ -20,7 +20,7 @@ class AddCreditsView extends BaseMigration
         $podcastPersonsTable = $this->db->prefixTable('podcasts_persons');
         $episodePersonsTable = $this->db->prefixTable('episodes_persons');
         $episodesTable = $this->db->prefixTable('episodes');
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
         CREATE VIEW `{$viewName}` AS
             SELECT `person_group`, `person_id`, `full_name`, `person_role`, `podcast_id`, NULL AS `episode_id` FROM `{$podcastPersonsTable}`
                 INNER JOIN `{$personsTable}`
@@ -33,7 +33,7 @@ class AddCreditsView extends BaseMigration
                     ON (`episode_id`=`{$episodesTable}`.`id`)
             WHERE `{$episodesTable}`.published_at <= UTC_TIMESTAMP()
             ORDER BY `person_group`, `full_name`, `person_role`, `podcast_id`, `episode_id`;
-        CODE_SAMPLE;
+        SQL;
         $this->db->query($createQuery);
     }
 
diff --git a/app/Database/Migrations/2022-02-23-100000_add_episode_id_to_posts.php b/app/Database/Migrations/2022-02-23-100000_add_episode_id_to_posts.php
index c22cb5f8ad4a57cbe5de538324a4acfcc99f1bd9..ecceb7e1cc0391e29a8e905fe709887c73fa97c9 100644
--- a/app/Database/Migrations/2022-02-23-100000_add_episode_id_to_posts.php
+++ b/app/Database/Migrations/2022-02-23-100000_add_episode_id_to_posts.php
@@ -17,10 +17,8 @@ class AddEpisodeIdToPosts extends BaseMigration
     public function up(): void
     {
         $prefix = $this->db->getPrefix();
-        $fediverseTablesPrefix = config('Fediverse')
-            ->tablesPrefix;
 
-        $this->forge->addColumn("{$fediverseTablesPrefix}posts", [
+        $this->forge->addColumn('fediverse_posts', [
             'episode_id' => [
                 'type'     => 'INT',
                 'unsigned' => true,
@@ -29,22 +27,22 @@ class AddEpisodeIdToPosts extends BaseMigration
             ],
         ]);
 
-        $alterQuery = <<<CODE_SAMPLE
-            ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
-            ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
-        CODE_SAMPLE;
-        $this->db->query($alterQuery);
+        $this->forge->addForeignKey(
+            'episode_id',
+            'episodes',
+            'id',
+            '',
+            'CASCADE',
+            $prefix . 'fediverse_posts_episode_id_foreign'
+        );
+        $this->forge->processIndexes('fediverse_posts');
     }
 
     public function down(): void
     {
-        $fediverseTablesPrefix = config('Fediverse')
-            ->tablesPrefix;
+        $prefix = $this->db->getPrefix();
 
-        $this->forge->dropForeignKey(
-            $fediverseTablesPrefix . 'posts',
-            $fediverseTablesPrefix . 'posts_episode_id_foreign'
-        );
-        $this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'episode_id');
+        $this->forge->dropForeignKey('fediverse_posts', $prefix . 'fediverse_posts_episode_id_foreign');
+        $this->forge->dropColumn('fediverse_posts', 'episode_id');
     }
 }
diff --git a/app/Database/Migrations/2022-03-09-113000_add_created_by_to_posts.php b/app/Database/Migrations/2022-03-09-113000_add_created_by_to_posts.php
index ccd6de671a8e4d7c09c838e003d93c6bfb6ba8ce..93ff2022acb9e9baac9ad31b2abacb39d5f5bce8 100644
--- a/app/Database/Migrations/2022-03-09-113000_add_created_by_to_posts.php
+++ b/app/Database/Migrations/2022-03-09-113000_add_created_by_to_posts.php
@@ -17,10 +17,8 @@ class AddCreatedByToPosts extends BaseMigration
     public function up(): void
     {
         $prefix = $this->db->getPrefix();
-        $fediverseTablesPrefix = config('Fediverse')
-            ->tablesPrefix;
 
-        $this->forge->addColumn("{$fediverseTablesPrefix}posts", [
+        $this->forge->addColumn('fediverse_posts', [
             'created_by' => [
                 'type'     => 'INT',
                 'unsigned' => true,
@@ -29,22 +27,22 @@ class AddCreatedByToPosts extends BaseMigration
             ],
         ]);
 
-        $alterQuery = <<<CODE_SAMPLE
-            ALTER TABLE {$prefix}{$fediverseTablesPrefix}posts
-            ADD FOREIGN KEY {$prefix}{$fediverseTablesPrefix}posts_created_by_foreign(created_by) REFERENCES {$prefix}users(id) ON DELETE CASCADE;
-        CODE_SAMPLE;
-        $this->db->query($alterQuery);
+        $this->forge->addForeignKey(
+            'created_by',
+            'users',
+            'id',
+            '',
+            'CASCADE',
+            $prefix . 'fediverse_posts_created_by_foreign'
+        );
+        $this->forge->processIndexes('fediverse_posts');
     }
 
     public function down(): void
     {
-        $fediverseTablesPrefix = config('Fediverse')
-            ->tablesPrefix;
+        $prefix = $this->db->getPrefix();
 
-        $this->forge->dropForeignKey(
-            $fediverseTablesPrefix . 'posts',
-            $fediverseTablesPrefix . 'posts_created_by_foreign'
-        );
-        $this->forge->dropColumn($fediverseTablesPrefix . 'posts', 'created_by');
+        $this->forge->dropForeignKey('fediverse_posts', $prefix . 'fediverse_posts_created_by_foreign');
+        $this->forge->dropColumn('fediverse_posts', 'created_by');
     }
 }
diff --git a/app/Database/Migrations/2023-06-12-010000_add_full_text_search_indexes.php b/app/Database/Migrations/2023-06-12-010000_add_full_text_search_indexes.php
index d50515301791ca6fcf92cb1aaa1c7c5227f953a1..310ce8f31f8df2cb57dfb117e96993938c24c63e 100644
--- a/app/Database/Migrations/2023-06-12-010000_add_full_text_search_indexes.php
+++ b/app/Database/Migrations/2023-06-12-010000_add_full_text_search_indexes.php
@@ -10,23 +10,23 @@ class AddFullTextSearchIndexes extends BaseMigration
     {
         $prefix = $this->db->getPrefix();
 
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
             ALTER TABLE {$prefix}episodes DROP INDEX IF EXISTS title;
-        CODE_SAMPLE;
+        SQL;
 
         $this->db->query($createQuery);
 
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
             ALTER TABLE {$prefix}episodes
             ADD FULLTEXT episodes_search (title, description_markdown, slug, location_name);
-        CODE_SAMPLE;
+        SQL;
 
         $this->db->query($createQuery);
 
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
             ALTER TABLE {$prefix}podcasts
             ADD FULLTEXT podcasts_search (title, description_markdown, handle, location_name);
-        CODE_SAMPLE;
+        SQL;
 
         $this->db->query($createQuery);
     }
@@ -35,17 +35,17 @@ class AddFullTextSearchIndexes extends BaseMigration
     {
         $prefix = $this->db->getPrefix();
 
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
             ALTER TABLE {$prefix}episodes
             DROP INDEX IF EXISTS  episodes_search;
-        CODE_SAMPLE;
+        SQL;
 
         $this->db->query($createQuery);
 
-        $createQuery = <<<CODE_SAMPLE
+        $createQuery = <<<SQL
             ALTER TABLE {$prefix}podcasts
             DROP INDEX IF EXISTS  podcasts_search;
-        CODE_SAMPLE;
+        SQL;
 
         $this->db->query($createQuery);
     }
diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php
index 3528bfe80bd3e562e3f0dc498211783d23b34b2c..d8eab7bc036c1db85bd0926c95d704c3f005a775 100644
--- a/app/Entities/Clip/BaseClip.php
+++ b/app/Entities/Clip/BaseClip.php
@@ -57,7 +57,8 @@ class BaseClip extends Entity
     protected ?float $end_time = null;
 
     /**
-     * @var string[]
+     * @var array<int, string>
+     * @phpstan-var list<string>
      */
     protected $dates = ['created_at', 'updated_at', 'job_started_at', 'job_ended_at'];
 
diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php
index c7af02d3f007ce66b9d9f25074c056902ac6805f..c51e1e6dee49d36b45f4eeaa95e8a8faa3fa8ddf 100644
--- a/app/Entities/Episode.php
+++ b/app/Entities/Episode.php
@@ -144,7 +144,8 @@ class Episode extends Entity
     protected ?string $publication_status = null;
 
     /**
-     * @var string[]
+     * @var array<int, string>
+     * @phpstan-var list<string>
      */
     protected $dates = ['published_at', 'created_at', 'updated_at'];
 
diff --git a/app/Entities/EpisodeComment.php b/app/Entities/EpisodeComment.php
index 84e2025985636260be45f8bca40f2728c7c84c00..5b10d1333387b1634b9d8353355662d7b436e5c7 100644
--- a/app/Entities/EpisodeComment.php
+++ b/app/Entities/EpisodeComment.php
@@ -51,7 +51,8 @@ class EpisodeComment extends UuidEntity
     protected bool $has_replies = false;
 
     /**
-     * @var string[]
+     * @var array<int, string>
+     * @phpstan-var list<string>
      */
     protected $dates = ['created_at'];
 
diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php
index 0ab77601a5daadd639a7c46caeb4e11534f27417..d57a1463c806c2a8507c9672da1af89450f33a3b 100644
--- a/app/Entities/Podcast.php
+++ b/app/Entities/Podcast.php
@@ -166,7 +166,8 @@ class Podcast extends Entity
     protected ?string $publication_status = null;
 
     /**
-     * @var string[]
+     * @var array<int, string>
+     * @phpstan-var list<string>
      */
     protected $dates = ['published_at', 'created_at', 'updated_at'];
 
diff --git a/app/Filters/AllowCorsFilter.php b/app/Filters/AllowCorsFilter.php
index 7edc2b6e96a67292d3747f8cbbe1f285f6e949d8..96d6409bf4aae5d02aad6f23f930fba4f7c16bc4 100644
--- a/app/Filters/AllowCorsFilter.php
+++ b/app/Filters/AllowCorsFilter.php
@@ -10,11 +10,17 @@ use CodeIgniter\HTTP\ResponseInterface;
 
 class AllowCorsFilter implements FilterInterface
 {
+    /**
+     * @param string[]|null $arguments
+     */
     public function before(RequestInterface $request, $arguments = null): void
     {
         // Do something here
     }
 
+    /**
+     * @param string[]|null $arguments
+     */
     public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): void
     {
         if (! $response->hasHeader('Cache-Control')) {
diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php
index 176e75c7ae3d4497e2c7859afbf791ff0c8aab77..fdc77f21a4dbfcf343ffd701ccff619d78af35ea 100644
--- a/app/Helpers/components_helper.php
+++ b/app/Helpers/components_helper.php
@@ -206,7 +206,7 @@ if (! function_exists('publication_status_banner')) {
                 $bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode');
                 $bannerText = lang('Podcast.publication_status_banner.scheduled', [
                     'publication_date' => local_datetime($publicationDate),
-                ], null, false);
+                ]);
                 $linkRoute = route_to('podcast-publish_edit', $podcastId);
                 $linkLabel = lang('Podcast.publish_edit');
                 break;
@@ -492,10 +492,10 @@ if (! function_exists('category_label')) {
     {
         $categoryLabel = '';
         if ($category->parent_id !== null) {
-            $categoryLabel .= lang('Podcast.category_options.' . $category->parent->code, [], null, false) . ' › ';
+            $categoryLabel .= lang('Podcast.category_options.' . $category->parent->code) . ' › ';
         }
 
-        return $categoryLabel . lang('Podcast.category_options.' . $category->code, [], null, false);
+        return $categoryLabel . lang('Podcast.category_options.' . $category->code);
     }
 }
 
diff --git a/app/Libraries/RouteCollection.php b/app/Libraries/RouteCollection.php
index 87576d341c41efcb07752098d8782a228377f943..75ea475799492bec2a5d8d57701a3498a0289d41 100644
--- a/app/Libraries/RouteCollection.php
+++ b/app/Libraries/RouteCollection.php
@@ -14,18 +14,25 @@ declare(strict_types=1);
 
 namespace App\Libraries;
 
+use Closure;
 use CodeIgniter\Router\RouteCollection as CodeIgniterRouteCollection;
 
 class RouteCollection extends CodeIgniterRouteCollection
 {
+    /**
+     * The current hostname from $_SERVER['HTTP_HOST']
+     */
+    private ?string $httpHost = null;
+
     /**
      * Does the heavy lifting of creating an actual route. You must specify
      * the request method(s) that this route will work for. They can be separated
      * by a pipe character "|" if there is more than one.
      *
-     * @param array|Closure|string $to
+     * @param array<int, mixed>|Closure|string $to
+     * @param array<string, mixed> $options
      */
-    protected function create(string $verb, string $from, $to, ?array $options = null)
+    protected function create(string $verb, string $from, $to, ?array $options = null): void
     {
         $overwrite = false;
         $prefix = $this->group === null ? '' : $this->group . '/';
@@ -81,8 +88,8 @@ class RouteCollection extends CodeIgniterRouteCollection
             // Get a constant string to work with.
             $to = preg_replace('/(\$\d+)/', '$X', $to);
 
-            for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; $i++) {
-                $to = preg_replace_callback('/\$X/', static fn ($m) => '$' . $i, $to, 1);
+            for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; ++$i) {
+                $to = preg_replace_callback('/\$X/', static fn ($m): string => '$' . $i, $to, 1);
             }
         }
 
@@ -97,7 +104,7 @@ class RouteCollection extends CodeIgniterRouteCollection
             // If no namespace found, add the default namespace
             if (strpos($to, '\\') === false || strpos($to, '\\') > 0) {
                 $namespace = $options['namespace'] ?? $this->defaultNamespace;
-                $to = trim($namespace, '\\') . '\\' . $to;
+                $to = trim((string) $namespace, '\\') . '\\' . $to;
             }
             // Always ensure that we escape our namespace so we're not pointing to
             // \CodeIgniter\Routes\Controller::method.
@@ -134,4 +141,140 @@ class RouteCollection extends CodeIgniterRouteCollection
             $this->routes['*'][$name]['redirect'] = $options['redirect'];
         }
     }
+
+    /**
+     * Compares the hostname passed in against the current hostname
+     * on this page request.
+     *
+     * @param string $hostname Hostname in route options
+     */
+    private function checkHostname($hostname): bool
+    {
+        // CLI calls can't be on hostname.
+        if ($this->httpHost === null) {
+            return false;
+        }
+
+        return strtolower($this->httpHost) === strtolower($hostname);
+    }
+
+    /**
+     * @param array<int, mixed> $to
+     *
+     * @return string|array<int, mixed>
+     */
+    private function processArrayCallableSyntax(string $from, array $to): string | array
+    {
+        // [classname, method]
+        // eg, [Home::class, 'index']
+        if (is_callable($to, true, $callableName)) {
+            // If the route has placeholders, add params automatically.
+            $params = $this->getMethodParams($from);
+
+            return '\\' . $callableName . $params;
+        }
+
+        // [[classname, method], params]
+        // eg, [[Home::class, 'index'], '$1/$2']
+        if (
+            isset($to[0], $to[1])
+            && is_callable($to[0], true, $callableName)
+            && is_string($to[1])
+        ) {
+            return '\\' . $callableName . '/' . $to[1];
+        }
+
+        return $to;
+    }
+
+    /**
+     * Compares the subdomain(s) passed in against the current subdomain
+     * on this page request.
+     *
+     * @param string|string[] $subdomains
+     */
+    private function checkSubdomains($subdomains): bool
+    {
+        // CLI calls can't be on subdomain.
+        if ($this->httpHost === null) {
+            return false;
+        }
+
+        if ($this->currentSubdomain === null) {
+            $this->currentSubdomain = $this->determineCurrentSubdomain();
+        }
+
+        if (! is_array($subdomains)) {
+            $subdomains = [$subdomains];
+        }
+
+        // Routes can be limited to any sub-domain. In that case, though,
+        // it does require a sub-domain to be present.
+        if (! empty($this->currentSubdomain) && in_array('*', $subdomains, true)) {
+            return true;
+        }
+
+        return in_array($this->currentSubdomain, $subdomains, true);
+    }
+
+    /**
+     * Returns the method param string like `/$1/$2` for placeholders
+     */
+    private function getMethodParams(string $from): string
+    {
+        preg_match_all('/\(.+?\)/', $from, $matches);
+        $count = is_countable($matches[0]) ? count($matches[0]) : 0;
+
+        $params = '';
+
+        for ($i = 1; $i <= $count; ++$i) {
+            $params .= '/$' . $i;
+        }
+
+        return $params;
+    }
+
+    /**
+     * Examines the HTTP_HOST to get the best match for the subdomain. It
+     * won't be perfect, but should work for our needs.
+     *
+     * It's especially not perfect since it's possible to register a domain
+     * with a period (.) as part of the domain name.
+     *
+     * @return false|string the subdomain
+     */
+    private function determineCurrentSubdomain()
+    {
+        // We have to ensure that a scheme exists
+        // on the URL else parse_url will mis-interpret
+        // 'host' as the 'path'.
+        $url = $this->httpHost;
+        if (strpos($url, 'http') !== 0) {
+            $url = 'http://' . $url;
+        }
+
+        $parsedUrl = parse_url($url);
+
+        $host = explode('.', $parsedUrl['host']);
+
+        if ($host[0] === 'www') {
+            unset($host[0]);
+        }
+
+        // Get rid of any domains, which will be the last
+        unset($host[count($host) - 1]);
+
+        // Account for .co.uk, .co.nz, etc. domains
+        if (end($host) === 'co') {
+            $host = array_slice($host, 0, -1);
+        }
+
+        // If we only have 1 part left, then we don't have a sub-domain.
+        if (count($host) === 1) {
+            // Set it to false so we don't make it back here again.
+            return false;
+        }
+
+        return array_shift($host);
+    }
 }
diff --git a/app/Libraries/ViewComponents/ComponentRenderer.php b/app/Libraries/ViewComponents/ComponentRenderer.php
index 7eb0984d21b5ca2e36f60857deb718837f64a574..f9e916c045d4ca32f0fa5e5180139863282168c0 100644
--- a/app/Libraries/ViewComponents/ComponentRenderer.php
+++ b/app/Libraries/ViewComponents/ComponentRenderer.php
@@ -82,7 +82,7 @@ class ComponentRenderer
             $matches[name] = tag name
             $matches[attributes] = array of attribute string (class="foo")
          */
-        return preg_replace_callback($pattern, function ($match): string {
+        return preg_replace_callback($pattern, function (array $match): string {
             $view = $this->locateView($match['name']);
             $attributes = $this->parseAttributes($match['attributes']);
 
@@ -104,7 +104,7 @@ class ComponentRenderer
             $matches[attributes] = string of tag attributes (class="foo")
             $matches[slot] = the content inside the tags
          */
-        return preg_replace_callback($pattern, function ($match): string {
+        return preg_replace_callback($pattern, function (array $match): string {
             $view = $this->locateView($match['name']);
             $attributes = $this->parseAttributes($match['attributes']);
             $attributes['slot'] = $match['slot'];
diff --git a/app/Libraries/Vite/Vite.php b/app/Libraries/Vite/Vite.php
index 57d7ddc0403bbaa2aaf1732a5a6b23f13d8f24ba..64b4202d991ea9d8242ac0d476fe011614833192 100644
--- a/app/Libraries/Vite/Vite.php
+++ b/app/Libraries/Vite/Vite.php
@@ -94,13 +94,13 @@ class Vite
     private function getHtmlTag(string $assetUrl, string $type): string
     {
         return match ($type) {
-            'css' => <<<CODE_SAMPLE
+            'css' => <<<HTML
                 <link rel="stylesheet" href="{$assetUrl}"/>
-            CODE_SAMPLE
+            HTML
             ,
-            'js' => <<<CODE_SAMPLE
+            'js' => <<<HTML
                     <script type="module" src="{$assetUrl}"></script>
-                CODE_SAMPLE
+                HTML
             ,
             default => '',
         };
diff --git a/app/Models/CategoryModel.php b/app/Models/CategoryModel.php
index 50ab67fbe88a737e4a5537f4367094c4dd988547..235139d9fc6bb3889d9c09a462186acf5db4360b 100644
--- a/app/Models/CategoryModel.php
+++ b/app/Models/CategoryModel.php
@@ -67,15 +67,10 @@ class CategoryModel extends Model
                 static function (array $result, Category $category): array {
                     $result[$category->id] = '';
                     if ($category->parent instanceof Category) {
-                        $result[$category->id] = lang(
-                            'Podcast.category_options.' . $category->parent->code,
-                            [],
-                            null,
-                            false
-                        ) . ' › ';
+                        $result[$category->id] = lang('Podcast.category_options.' . $category->parent->code) . ' › ';
                     }
 
-                    $result[$category->id] .= lang('Podcast.category_options.' . $category->code, [], null, false);
+                    $result[$category->id] .= lang('Podcast.category_options.' . $category->code);
                     return $result;
                 },
                 [],
diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php
index 862177c597301c0bcd0405a8e6dfd99cc4dc16e8..bbceb48f5abf5f1be473d9d95593cc9c861d5b4a 100644
--- a/app/Models/EpisodeCommentModel.php
+++ b/app/Models/EpisodeCommentModel.php
@@ -216,7 +216,7 @@ class EpisodeCommentModel extends UuidModel
         )
             ->whereIn('in_reply_to_id', static function (BaseBuilder $builder) use (&$episodeId): BaseBuilder {
                 return $builder->select('id')
-                    ->from(config('Fediverse')->tablesPrefix . 'posts')
+                    ->from('fediverse_posts')
                     ->where([
                         'episode_id'     => $episodeId,
                         'in_reply_to_id' => null,
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index aceddab84011335a9c00161f9621fa3df64ea289..339acfb54c8fbc3cada956bde37a5a04b0397941 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -382,13 +382,11 @@ class EpisodeModel extends UuidModel
             ->groupBy('episode_id')
             ->getCompiledSelect();
 
-        $postsTable = config('Fediverse')
-            ->tablesPrefix . 'posts';
         $episodePostsRepliesCount = (new PostModel())->builder()
-            ->select($postsTable . '.episode_id as episode_id, COUNT(*) as `comments_count`')
-            ->join($postsTable . ' as fp', $postsTable . '.id = fp.in_reply_to_id')
-            ->where($postsTable . '.in_reply_to_id', null)
-            ->groupBy($postsTable . '.episode_id')
+            ->select('fediverse_posts.episode_id as episode_id, COUNT(*) as `comments_count`')
+            ->join('fediverse_posts as fp', 'fediverse_posts.id = fp.in_reply_to_id')
+            ->where('fediverse_posts.in_reply_to_id', null)
+            ->groupBy('fediverse_posts.episode_id')
             ->getCompiledSelect();
 
         /** @var BaseResult $query */
@@ -409,11 +407,7 @@ class EpisodeModel extends UuidModel
     {
         $episodePostsCount = $this->builder()
             ->select('episodes.id, COUNT(*) as `posts_count`')
-            ->join(
-                config('Fediverse')
-                    ->tablesPrefix . 'posts',
-                'episodes.id = ' . config('Fediverse')->tablesPrefix . 'posts.episode_id'
-            )
+            ->join('fediverse_posts', 'episodes.id = fediverse_posts.episode_id')
             ->where('in_reply_to_id', null)
             ->groupBy('episodes.id')
             ->get()
diff --git a/app/Models/PersonModel.php b/app/Models/PersonModel.php
index 83dad16233aee40469aa67b622e5ef8bb5b7f432..50f1fa4f7f0a9cff5105cc276a1254b641877976 100644
--- a/app/Models/PersonModel.php
+++ b/app/Models/PersonModel.php
@@ -145,7 +145,7 @@ class PersonModel extends Model
                 $this->select('`id`, `full_name`')
                     ->orderBy('`full_name`', 'ASC')
                     ->findAll(),
-                static function ($result, $person) {
+                static function (array $result, $person): array {
                     $result[$person->id] = $person->full_name;
                     return $result;
                 },
diff --git a/app/Models/PlatformModel.php b/app/Models/PlatformModel.php
index dc9475bf0e45aebb3ea1a909b05a970e09b3264e..14219f809be0e68e8cdea70b9136206b630c5995 100644
--- a/app/Models/PlatformModel.php
+++ b/app/Models/PlatformModel.php
@@ -158,12 +158,12 @@ class PlatformModel extends Model
         $podcastsPlatformsTable = $this->db->prefixTable('podcasts_platforms');
         $platformsTable = $this->db->prefixTable('platforms');
 
-        $deleteJoinQuery = <<<CODE_SAMPLE
+        $deleteJoinQuery = <<<SQL
         DELETE {$podcastsPlatformsTable}
         FROM {$podcastsPlatformsTable}
         INNER JOIN {$platformsTable} ON {$platformsTable}.slug = {$podcastsPlatformsTable}.platform_slug
         WHERE `podcast_id` = ? AND `type` = ?
-        CODE_SAMPLE;
+        SQL;
 
         $this->db->query($deleteJoinQuery, [$podcastId, $platformType]);
 
diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php
index 0e5b732c38c3bbb7e791f8e35c8c1211f14632c7..f64d7915679dc91877ff835adff981eccf00abe4 100644
--- a/app/Models/PodcastModel.php
+++ b/app/Models/PodcastModel.php
@@ -84,6 +84,7 @@ class PodcastModel extends Model
      * @var array<string, string>
      */
     protected $validationRules = [
+        'id'                   => 'permit_empty|is_natural_no_zero',
         'title'                => 'required',
         'handle'               => 'required|regex_match[/^[a-zA-Z0-9\_]{1,32}$/]|is_unique[podcasts.handle,id,{id}]',
         'description_markdown' => 'required',
@@ -174,23 +175,15 @@ class PodcastModel extends Model
         $prefix = $this->db->getPrefix();
 
         if ($orderBy === 'activity') {
-            $fediverseTablePrefix = $prefix . config('Fediverse')
-                ->tablesPrefix;
             $this->builder()
-                ->select(
-                    'podcasts.*, MAX(' . $fediverseTablePrefix . 'posts.published_at' . ') as max_published_at'
-                )
-                ->join(
-                    $fediverseTablePrefix . 'posts',
-                    $fediverseTablePrefix . 'posts.actor_id = podcasts.actor_id',
-                    'left'
-                )
+                ->select('podcasts.*, MAX(`' . $prefix . 'fediverse_posts`.`published_at`) as max_published_at')
+                ->join('fediverse_posts', 'fediverse_posts.actor_id = podcasts.actor_id', 'left')
                 ->groupStart()
                 ->where(
-                    '`' . $fediverseTablePrefix . 'posts`.`published_at` <= UTC_TIMESTAMP()',
+                    '`' . $prefix . 'fediverse_posts`.`published_at` <= UTC_TIMESTAMP()',
                     null,
                     false
-                )->orWhere($fediverseTablePrefix . 'posts.published_at', null)
+                )->orWhere('fediverse_posts.published_at', null)
                 ->groupEnd()
                 ->groupBy('podcasts.actor_id')
                 ->orderBy('max_published_at', 'DESC');
diff --git a/app/Models/PostModel.php b/app/Models/PostModel.php
index 32639a9fad38e27ae38826530bf315dceb1e1606..ae409903c5e1f79a7d4b6be132be685184fd0161 100644
--- a/app/Models/PostModel.php
+++ b/app/Models/PostModel.php
@@ -58,8 +58,8 @@ class PostModel extends FediversePostModel
     public function setEpisodeIdForRepliesOfEpisodePosts(): int | false
     {
         // make sure that posts in reply to episode activities have an episode id
-        $postsToUpdate = $this->db->table(config('Fediverse')->tablesPrefix . 'posts as p1')
-            ->join(config('Fediverse')->tablesPrefix . 'posts as p2', 'p1.id = p2.in_reply_to_id')
+        $postsToUpdate = $this->db->table('fediverse_posts as p1')
+            ->join('fediverse_posts as p2', 'p1.id = p2.in_reply_to_id')
             ->select('p2.id, p1.episode_id')
             ->where([
                 'p2.in_reply_to_id IS NOT' => null,
diff --git a/modules/Auth/Commands/RolesDoc.php b/modules/Auth/Commands/RolesDoc.php
index 06e263ae64246d408e30cb063bf52cf259f074eb..6e15583e1a2ece9f807fbd54a0f78718dfc813f3 100644
--- a/modules/Auth/Commands/RolesDoc.php
+++ b/modules/Auth/Commands/RolesDoc.php
@@ -82,7 +82,7 @@ class RolesDoc extends BaseCommand
             $pattern,
             ['role', 'description', 'permissions'],
             $authGroups->instanceGroups,
-            static function ($table, $key, $value) use ($instanceMatrix): void {
+            static function ($table, $key, array $value) use ($instanceMatrix): void {
                 $table->addRow($value['title'], $value['description'], implode(', ', $instanceMatrix[$key]));
             }
         );
@@ -109,7 +109,7 @@ class RolesDoc extends BaseCommand
             $pattern,
             ['role', 'description', 'permissions'],
             $authGroups->podcastGroups,
-            static function ($table, $key, $value) use ($podcastMatrix): void {
+            static function ($table, $key, array $value) use ($podcastMatrix): void {
                 $table->addRow($value['title'], $value['description'], implode(', ', $podcastMatrix[$key]));
             }
         );
diff --git a/modules/Auth/Controllers/ContributorController.php b/modules/Auth/Controllers/ContributorController.php
index 2eb6acc736452168ef24659a268a4ecb7a0512c9..d976bb413c40b91b3fe0ed847a7ff740d60f4704 100644
--- a/modules/Auth/Controllers/ContributorController.php
+++ b/modules/Auth/Controllers/ContributorController.php
@@ -83,7 +83,7 @@ class ContributorController extends BaseController
         $users = (new UserModel())->findAll();
         $contributorOptions = array_reduce(
             $users,
-            static function ($result, $user) {
+            static function (array $result, $user): array {
                 $result[$user->id] = $user->username;
                 return $result;
             },
@@ -94,7 +94,7 @@ class ContributorController extends BaseController
         $roleOptions = [];
         array_walk(
             $roles,
-            static function ($role, $key) use (&$roleOptions): array {
+            static function (string $role, $key) use (&$roleOptions): array {
                 $roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title');
                 return $roleOptions;
             },
@@ -137,7 +137,7 @@ class ContributorController extends BaseController
         $roleOptions = [];
         array_walk(
             $roles,
-            static function ($role) use (&$roleOptions): array {
+            static function (string $role) use (&$roleOptions): array {
                 $roleOptions[$role] = lang('Auth.podcast_groups.' . $role . '.title');
                 return $roleOptions;
             },
diff --git a/modules/Auth/Controllers/UserController.php b/modules/Auth/Controllers/UserController.php
index 1a5d059e5d76494fc0d60452503da68be606236d..9da5d0ed91fe66980bb182c536814da948f2f792 100644
--- a/modules/Auth/Controllers/UserController.php
+++ b/modules/Auth/Controllers/UserController.php
@@ -66,7 +66,7 @@ class UserController extends BaseController
         $roleOptions = [];
         array_walk(
             $roles,
-            static function ($role, $key) use (&$roleOptions): array {
+            static function (array $role, $key) use (&$roleOptions): array {
                 $roleOptions[$key] = $role['title'];
                 return $roleOptions;
             },
@@ -172,7 +172,7 @@ class UserController extends BaseController
         $roleOptions = [];
         array_walk(
             $roles,
-            static function ($role, $key) use (&$roleOptions): array {
+            static function (array $role, $key) use (&$roleOptions): array {
                 $roleOptions[$key] = $role['title'];
                 return $roleOptions;
             },
diff --git a/modules/Fediverse/Config/Fediverse.php b/modules/Fediverse/Config/Fediverse.php
index f40fec3757bd145d9393a1fd18343c7f3e59321a..835567e0242b9f1523a732f119c890a8b1276875 100644
--- a/modules/Fediverse/Config/Fediverse.php
+++ b/modules/Fediverse/Config/Fediverse.php
@@ -38,8 +38,6 @@ class Fediverse extends BaseConfig
 
     public string $defaultCoverImageMimetype = 'image/jpeg';
 
-    public string $tablesPrefix = 'fediverse_';
-
     /**
      * --------------------------------------------------------------------
      * Cache options
diff --git a/modules/Fediverse/Controllers/ActorController.php b/modules/Fediverse/Controllers/ActorController.php
index 6048e7fefd25b48c662ec766ad1a038d4c166854..52f35e36d3a3f5fae69c289dbe02e3a8fc43b67b 100644
--- a/modules/Fediverse/Controllers/ActorController.php
+++ b/modules/Fediverse/Controllers/ActorController.php
@@ -291,14 +291,11 @@ class ActorController extends Controller
 
     public function followers(): ResponseInterface
     {
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         // get followers for a specific actor
         $followers = model('ActorModel', false)
-            ->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
-            ->where($tablesPrefix . 'follows.target_actor_id', $this->actor->id)
-            ->orderBy($tablesPrefix . 'follows.created_at', 'DESC');
+            ->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner')
+            ->where('fediverse_follows.target_actor_id', $this->actor->id)
+            ->orderBy('fediverse_follows.created_at', 'DESC');
 
         $pageNumber = (int) $this->request->getGet('page');
 
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php b/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php
index c2fa15d1bad7b0f3ad9bf44d3c26cdf4b79ae75f..ddf5228c91e9db01d3f4a4683d6d911a0635b80f 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-010000_add_actors.php
@@ -113,11 +113,11 @@ class AddActors extends BaseMigration
         $this->forge->addPrimaryKey('id');
         $this->forge->addUniqueKey('uri');
         $this->forge->addUniqueKey(['username', 'domain']);
-        $this->forge->createTable(config('Fediverse')->tablesPrefix . 'actors');
+        $this->forge->createTable('fediverse_actors');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'actors');
+        $this->forge->dropTable('fediverse_actors');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php b/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php
index 1f04ef2abbad2dd8b5dbc199707f4e1e219eadbc..903f8aec9610eb9e93dddec3ba4ef13aae22f364 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-020000_add_posts.php
@@ -75,21 +75,18 @@ class AddPosts extends BaseMigration
             ],
         ]);
 
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addPrimaryKey('id');
         $this->forge->addUniqueKey('uri');
         // FIXME: an actor must reblog a post only once
         // $this->forge->addUniqueKey(['actor_id', 'reblog_of_id']);
-        $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('in_reply_to_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('reblog_of_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
-        $this->forge->createTable($tablesPrefix . 'posts');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('in_reply_to_id', 'fediverse_posts', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('reblog_of_id', 'fediverse_posts', 'id', '', 'CASCADE');
+        $this->forge->createTable('fediverse_posts');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts');
+        $this->forge->dropTable('fediverse_posts');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php
index 2baa67c9a4e906744600a4f08b6cf0d2e129434e..18b94cdf24c490d89b39cd3e02395779be0d8f4c 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_activities.php
@@ -58,18 +58,15 @@ class AddActivities extends BaseMigration
             ],
         ]);
 
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addPrimaryKey('id');
-        $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
-        $this->forge->createTable($tablesPrefix . 'activities');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
+        $this->forge->createTable('fediverse_activities');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'activities');
+        $this->forge->dropTable('fediverse_activities');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php
index 33aaa8989ec15e53a2f85c094a79baa72bd4e2b1..75ec8f2175685bd7a0a25bfbe37698b98f719ebb 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_favourites.php
@@ -29,18 +29,15 @@ class AddFavourites extends BaseMigration
             ],
         ]);
 
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
         $this->forge->addPrimaryKey(['actor_id', 'post_id']);
-        $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
-        $this->forge->createTable($tablesPrefix . 'favourites');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
+        $this->forge->createTable('fediverse_favourites');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'favourites');
+        $this->forge->dropTable('fediverse_favourites');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php
index 9704a7a84ed84a0f9897b0b7c9b965456babafcf..0b0273ba02ff79965de29bf7c6474e7408fa6057 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_follows.php
@@ -31,18 +31,15 @@ class AddFollowers extends BaseMigration
             ],
         ]);
 
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
         $this->forge->addPrimaryKey(['actor_id', 'target_actor_id']);
-        $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->createTable($tablesPrefix . 'follows');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->createTable('fediverse_follows');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'follows');
+        $this->forge->dropTable('fediverse_follows');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php
index 9f1431845310a7f870c944272d7ccd0f70f7fc5c..f0359070dc598c82c7a8e61502794bc4ffbaae60 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-100000_add_preview_cards.php
@@ -75,11 +75,11 @@ class AddPreviewCards extends BaseMigration
 
         $this->forge->addPrimaryKey('id');
         $this->forge->addUniqueKey('url');
-        $this->forge->createTable(config('Fediverse')->tablesPrefix . 'preview_cards');
+        $this->forge->createTable('fediverse_preview_cards');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'preview_cards');
+        $this->forge->dropTable('fediverse_preview_cards');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php b/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php
index d6719bcc4d5a11031858dec591596d973df6f926..2bd9d9ba30c69387b3d228aae762a57c104339fe 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-110000_add_posts_preview_cards.php
@@ -29,17 +29,14 @@ class AddPostsPreviewCards extends BaseMigration
             ],
         ]);
 
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addPrimaryKey(['post_id', 'preview_card_id']);
-        $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('preview_card_id', $tablesPrefix . 'preview_cards', 'id', '', 'CASCADE');
-        $this->forge->createTable($tablesPrefix . 'posts_preview_cards');
+        $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('preview_card_id', 'fediverse_preview_cards', 'id', '', 'CASCADE');
+        $this->forge->createTable('fediverse_posts_preview_cards');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'posts_preview_cards');
+        $this->forge->dropTable('fediverse_posts_preview_cards');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php b/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php
index df4d15b27a64bbba46c61316d49cf3a5278ebc01..044a0e0b9927327a398377fa5a3b55c56132e63f 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-120000_add_blocked_domains.php
@@ -28,11 +28,11 @@ class AddBlockedDomains extends BaseMigration
             ],
         ]);
         $this->forge->addPrimaryKey('name');
-        $this->forge->createTable(config('Fediverse')->tablesPrefix . 'blocked_domains');
+        $this->forge->createTable('fediverse_blocked_domains');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'blocked_domains');
+        $this->forge->dropTable('fediverse_blocked_domains');
     }
 }
diff --git a/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php b/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php
index 78b0ab2455a2aed560651b9ce8f9887ee7c5b51e..df979192d0f2a885277955a2bce0bebeabff8594 100644
--- a/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php
+++ b/modules/Fediverse/Database/Migrations/2018-01-01-130000_add_notifications.php
@@ -55,19 +55,16 @@ class AddNotifications extends BaseMigration
             ],
         ]);
 
-        $tablesPrefix = config('Fediverse')
-            ->tablesPrefix;
-
         $this->forge->addPrimaryKey('id');
-        $this->forge->addForeignKey('actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('target_actor_id', $tablesPrefix . 'actors', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('post_id', $tablesPrefix . 'posts', 'id', '', 'CASCADE');
-        $this->forge->addForeignKey('activity_id', $tablesPrefix . 'activities', 'id', '', 'CASCADE');
-        $this->forge->createTable($tablesPrefix . 'notifications');
+        $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('target_actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('post_id', 'fediverse_posts', 'id', '', 'CASCADE');
+        $this->forge->addForeignKey('activity_id', 'fediverse_activities', 'id', '', 'CASCADE');
+        $this->forge->createTable('fediverse_notifications');
     }
 
     public function down(): void
     {
-        $this->forge->dropTable(config('Fediverse')->tablesPrefix . 'notifications');
+        $this->forge->dropTable('fediverse_notifications');
     }
 }
diff --git a/modules/Fediverse/Helpers/fediverse_helper.php b/modules/Fediverse/Helpers/fediverse_helper.php
index ee1d015e9ae0ff524999f567e10434fbe9ce0b33..53f662f231eecde1d7d64ba7257cb30d89c7a7b0 100644
--- a/modules/Fediverse/Helpers/fediverse_helper.php
+++ b/modules/Fediverse/Helpers/fediverse_helper.php
@@ -408,7 +408,7 @@ if (! function_exists('linkify')) {
                 ),
                 'handle' => preg_replace_callback(
                     '~(?<!\w)@(?<username>\w++)(?:@(?<domain>(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]))?~',
-                    static function ($match) use (&$links) {
+                    static function (array $match) use (&$links) {
                         // check if host is set and look for actor in database
                         if (isset($match['host'])) {
                             if (
@@ -489,7 +489,7 @@ if (! function_exists('linkify')) {
         // Insert all links
         return preg_replace_callback(
             '~<(\d+)>~',
-            static function ($match) use (&$links) {
+            static function (array $match) use (&$links): string {
                 return $links[$match[1] - 1];
             },
             $text,
diff --git a/modules/Fediverse/Models/ActivityModel.php b/modules/Fediverse/Models/ActivityModel.php
index 86b0c5dacf03edf747275f816ce35554ff2efab4..cf9181fe03a5bf5c35d75eaeae9186c7eb67a93e 100644
--- a/modules/Fediverse/Models/ActivityModel.php
+++ b/modules/Fediverse/Models/ActivityModel.php
@@ -13,14 +13,15 @@ namespace Modules\Fediverse\Models;
 use CodeIgniter\Database\BaseResult;
 use CodeIgniter\I18n\Time;
 use DateTimeInterface;
+use Michalsn\Uuid\UuidModel;
 use Modules\Fediverse\Entities\Activity;
 
-class ActivityModel extends BaseUuidModel
+class ActivityModel extends UuidModel
 {
     /**
      * @var string
      */
-    protected $table = 'activities';
+    protected $table = 'fediverse_activities';
 
     /**
      * @var string
diff --git a/modules/Fediverse/Models/ActorModel.php b/modules/Fediverse/Models/ActorModel.php
index 3b35583c26ffa3181446ee7f4091a92aae00405e..db07c0bea2a12acb44d0b476dedec652f0cb82bb 100644
--- a/modules/Fediverse/Models/ActorModel.php
+++ b/modules/Fediverse/Models/ActorModel.php
@@ -11,14 +11,15 @@ declare(strict_types=1);
 namespace Modules\Fediverse\Models;
 
 use CodeIgniter\Events\Events;
+use CodeIgniter\Model;
 use Modules\Fediverse\Entities\Actor;
 
-class ActorModel extends BaseModel
+class ActorModel extends Model
 {
     /**
      * @var string
      */
-    protected $table = 'actors';
+    protected $table = 'fediverse_actors';
 
     /**
      * @var string[]
@@ -119,10 +120,8 @@ class ActorModel extends BaseModel
             config('Fediverse')
                 ->cachePrefix . "actor#{$actorId}_followers";
         if (! ($found = cache($cacheName))) {
-            $tablesPrefix = config('Fediverse')
-                ->tablesPrefix;
-            $found = $this->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
-                ->where($tablesPrefix . 'follows.target_actor_id', $actorId)
+            $found = $this->join('fediverse_follows', 'fediverse_follows.actor_id = id', 'inner')
+                ->where('fediverse_follows.target_actor_id', $actorId)
                 ->findAll();
 
             cache()
@@ -225,28 +224,27 @@ class ActorModel extends BaseModel
             ->cachePrefix . 'blocked_actors';
         if (! ($found = cache($cacheName))) {
             $tablePrefix = config('Database')
-                ->default['DBPrefix'] . config('Fediverse')
-                ->tablesPrefix;
+                ->default['DBPrefix'];
             $result = $this->select('COUNT(DISTINCT `cp_fediverse_actors`.`id`) as `total_active_actors`', false)
                 ->join(
-                    $tablePrefix . 'posts',
-                    $tablePrefix . 'actors.id = ' . $tablePrefix . 'posts.actor_id',
+                    $tablePrefix . 'fediverse_posts',
+                    $tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_posts.actor_id',
                     'left outer'
                 )
                 ->join(
-                    $tablePrefix . 'favourites',
-                    $tablePrefix . 'actors.id = ' . $tablePrefix . 'favourites.actor_id',
+                    $tablePrefix . 'fediverse_favourites',
+                    $tablePrefix . 'fediverse_actors.id = ' . $tablePrefix . 'fediverse_favourites.actor_id',
                     'left outer'
                 )
                 ->where($tablePrefix . 'actors.domain', get_current_domain())
                 ->groupStart()
                 ->where(
-                    "`{$tablePrefix}posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
+                    "`{$tablePrefix}fediverse_posts`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
                     null,
                     false
                 )
                 ->orWhere(
-                    "`{$tablePrefix}favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
+                    "`{$tablePrefix}fediverse_favourites`.`created_at` >= UTC_TIMESTAMP() - INTERVAL {$lastNumberOfMonths} month",
                     null,
                     false
                 )
@@ -265,12 +263,8 @@ class ActorModel extends BaseModel
 
     public function resetFollowersCount(): int | false
     {
-        $tablePrefix = config('Fediverse')
-            ->tablesPrefix;
-
-        $actorsFollowersCount = $this->db->table($tablePrefix . 'follows')->select(
-            'target_actor_id as id, COUNT(*) as `followers_count`'
-        )
+        $actorsFollowersCount = $this->db->table('fediverse_follows')
+            ->select('target_actor_id as id, COUNT(*) as `followers_count`')
             ->groupBy('id')
             ->get()
             ->getResultArray();
@@ -284,10 +278,7 @@ class ActorModel extends BaseModel
 
     public function resetPostsCount(): int | false
     {
-        $tablePrefix = config('Fediverse')
-            ->tablesPrefix;
-
-        $actorsFollowersCount = $this->db->table($tablePrefix . 'posts')->select(
+        $actorsFollowersCount = $this->db->table($tablePrefix . 'fediverse_posts')->select(
             'actor_id as id, COUNT(*) as `posts_count`'
         )
             ->where([
diff --git a/modules/Fediverse/Models/BaseModel.php b/modules/Fediverse/Models/BaseModel.php
deleted file mode 100644
index 685f1776ecc0f4afd5a75e27f50af38dafbda965..0000000000000000000000000000000000000000
--- a/modules/Fediverse/Models/BaseModel.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Modules\Fediverse\Models;
-
-use CodeIgniter\Database\ConnectionInterface;
-use CodeIgniter\Model;
-use CodeIgniter\Validation\ValidationInterface;
-
-class BaseModel extends Model
-{
-    /**
-     * Model constructor.
-     *
-     * @param ConnectionInterface|null $db         DB Connection
-     * @param ValidationInterface|null $validation Validation
-     */
-    public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null)
-    {
-        parent::__construct($db, $validation);
-
-        $this->table = config('Fediverse')
-            ->tablesPrefix . $this->table;
-    }
-}
diff --git a/modules/Fediverse/Models/BaseUuidModel.php b/modules/Fediverse/Models/BaseUuidModel.php
deleted file mode 100644
index e2f80ea90d585438cc1126bf568062061d6fa9d9..0000000000000000000000000000000000000000
--- a/modules/Fediverse/Models/BaseUuidModel.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Modules\Fediverse\Models;
-
-use CodeIgniter\Database\ConnectionInterface;
-use CodeIgniter\Validation\ValidationInterface;
-use Michalsn\Uuid\UuidModel;
-
-class BaseUuidModel extends UuidModel
-{
-    public function __construct(ConnectionInterface &$db = null, ValidationInterface $validation = null)
-    {
-        parent::__construct($db, $validation);
-
-        $this->table = config('Fediverse')
-            ->tablesPrefix . $this->table;
-    }
-}
diff --git a/modules/Fediverse/Models/BlockedDomainModel.php b/modules/Fediverse/Models/BlockedDomainModel.php
index 933f6e522f926b9501891e026aaa5a7a7fe1c44d..e8e4f514e254e7bb0f1d7cb0eabdbfd69f96e97c 100644
--- a/modules/Fediverse/Models/BlockedDomainModel.php
+++ b/modules/Fediverse/Models/BlockedDomainModel.php
@@ -12,14 +12,15 @@ namespace Modules\Fediverse\Models;
 
 use CodeIgniter\Database\BaseResult;
 use CodeIgniter\Events\Events;
+use CodeIgniter\Model;
 use Modules\Fediverse\Entities\BlockedDomain;
 
-class BlockedDomainModel extends BaseModel
+class BlockedDomainModel extends Model
 {
     /**
      * @var string
      */
-    protected $table = 'blocked_domains';
+    protected $table = 'fediverse_blocked_domains';
 
     /**
      * @var string
diff --git a/modules/Fediverse/Models/FavouriteModel.php b/modules/Fediverse/Models/FavouriteModel.php
index 3188dd2d5696e36754ac5fe41f612a2b0ebca43c..72afc5a0bf0441d4ca271abf917b235cc7def638 100644
--- a/modules/Fediverse/Models/FavouriteModel.php
+++ b/modules/Fediverse/Models/FavouriteModel.php
@@ -11,18 +11,19 @@ declare(strict_types=1);
 namespace Modules\Fediverse\Models;
 
 use CodeIgniter\Events\Events;
+use Michalsn\Uuid\UuidModel;
 use Modules\Fediverse\Activities\LikeActivity;
 use Modules\Fediverse\Activities\UndoActivity;
 use Modules\Fediverse\Entities\Actor;
 use Modules\Fediverse\Entities\Favourite;
 use Modules\Fediverse\Entities\Post;
 
-class FavouriteModel extends BaseUuidModel
+class FavouriteModel extends UuidModel
 {
     /**
      * @var string
      */
-    protected $table = 'favourites';
+    protected $table = 'fediverse_favourites';
 
     /**
      * @var string[]
diff --git a/modules/Fediverse/Models/FollowModel.php b/modules/Fediverse/Models/FollowModel.php
index cf0cdcee70b41946768ee60f211bd79dc89d43f7..e866ba69acb18bfafddb3f8246c66aa28ee80aa8 100644
--- a/modules/Fediverse/Models/FollowModel.php
+++ b/modules/Fediverse/Models/FollowModel.php
@@ -12,18 +12,19 @@ namespace Modules\Fediverse\Models;
 
 use CodeIgniter\Events\Events;
 use CodeIgniter\I18n\Time;
+use CodeIgniter\Model;
 use Exception;
 use Modules\Fediverse\Activities\FollowActivity;
 use Modules\Fediverse\Activities\UndoActivity;
 use Modules\Fediverse\Entities\Actor;
 use Modules\Fediverse\Entities\Follow;
 
-class FollowModel extends BaseModel
+class FollowModel extends Model
 {
     /**
      * @var string
      */
-    protected $table = 'follows';
+    protected $table = 'fediverse_follows';
 
     /**
      * @var string[]
diff --git a/modules/Fediverse/Models/NotificationModel.php b/modules/Fediverse/Models/NotificationModel.php
index 24732d7d0da93495eb014a5de21142f4525319a5..53d4f0eb18f679a1d5a59c7eff920508d4134fbb 100644
--- a/modules/Fediverse/Models/NotificationModel.php
+++ b/modules/Fediverse/Models/NotificationModel.php
@@ -10,14 +10,15 @@ declare(strict_types=1);
 
 namespace Modules\Fediverse\Models;
 
+use Michalsn\Uuid\UuidModel;
 use Modules\Fediverse\Entities\Notification;
 
-class NotificationModel extends BaseUuidModel
+class NotificationModel extends UuidModel
 {
     /**
      * @var string
      */
-    protected $table = 'notifications';
+    protected $table = 'fediverse_notifications';
 
     /**
      * @var string
diff --git a/modules/Fediverse/Models/PostModel.php b/modules/Fediverse/Models/PostModel.php
index 7a07ea5b4323112b8351fe5a65d35f0236755732..c472a61bf9818e62a2bbb4471e9fbb3673e19de6 100644
--- a/modules/Fediverse/Models/PostModel.php
+++ b/modules/Fediverse/Models/PostModel.php
@@ -15,6 +15,7 @@ use CodeIgniter\Events\Events;
 use CodeIgniter\HTTP\URI;
 use CodeIgniter\I18n\Time;
 use Exception;
+use Michalsn\Uuid\UuidModel;
 use Modules\Fediverse\Activities\AnnounceActivity;
 use Modules\Fediverse\Activities\CreateActivity;
 use Modules\Fediverse\Activities\DeleteActivity;
@@ -23,12 +24,12 @@ use Modules\Fediverse\Entities\Actor;
 use Modules\Fediverse\Entities\Post;
 use Modules\Fediverse\Objects\TombstoneObject;
 
-class PostModel extends BaseUuidModel
+class PostModel extends UuidModel
 {
     /**
      * @var string
      */
-    protected $table = 'posts';
+    protected $table = 'fediverse_posts';
 
     /**
      * @var string
@@ -172,16 +173,10 @@ class PostModel extends BaseUuidModel
             ($withBlocked ? '_withBlocked' : '');
 
         if (! ($found = cache($cacheName))) {
-            $tablesPrefix = config('Fediverse')
-                ->tablesPrefix;
             if (! $withBlocked) {
-                $this->select($tablesPrefix . 'posts.*')
-                    ->join(
-                        $tablesPrefix . 'actors',
-                        $tablesPrefix . 'actors.id = ' . $tablesPrefix . 'posts.actor_id',
-                        'inner'
-                    )
-                    ->where($tablesPrefix . 'actors.is_blocked', 0);
+                $this->select('fediverse_posts.*')
+                    ->join('fediverse_actors', 'fediverse_actors.id = fediverse_posts.actor_id', 'inner')
+                    ->where('fediverse_actors.is_blocked', 0);
             }
 
             $this->where('in_reply_to_id', $this->uuid->fromString($postId) ->getBytes())
@@ -222,7 +217,7 @@ class PostModel extends BaseUuidModel
 
     public function addPreviewCard(string $postId, int $previewCardId): bool
     {
-        return $this->db->table(config('Fediverse')->tablesPrefix . 'posts_preview_cards')
+        return $this->db->table('fediverse_posts_preview_cards')
             ->insert([
                 'post_id' => $this->uuid->fromString($postId)
                     ->getBytes(),
@@ -370,7 +365,7 @@ class PostModel extends BaseUuidModel
         if (
             $post->preview_card &&
             $this->db
-                ->table(config('Fediverse')->tablesPrefix . 'posts_preview_cards')
+                ->table('fediverse_posts_preview_cards')
                 ->where('preview_card_id', $post->preview_card->id)
                 ->countAll() <= 1
         ) {
@@ -599,11 +594,9 @@ class PostModel extends BaseUuidModel
         $cacheName = config('Fediverse')
             ->cachePrefix . 'blocked_actors';
         if (! ($found = cache($cacheName))) {
-            $tablePrefix = config('Fediverse')
-                ->tablesPrefix;
             $result = $this->select('COUNT(*) as total_local_posts')
-                ->join($tablePrefix . 'actors', $tablePrefix . 'actors.id = ' . $tablePrefix . 'posts.actor_id')
-                ->where($tablePrefix . 'actors.domain', get_current_domain())
+                ->join('fediverse_actors', 'fediverse_actors.id = fediverse_posts.actor_id')
+                ->where('fediverse_actors.domain', get_current_domain())
                 ->where('`published_at` <= UTC_TIMESTAMP()', null, false)
                 ->get()
                 ->getResultArray();
@@ -619,12 +612,8 @@ class PostModel extends BaseUuidModel
 
     public function resetFavouritesCount(): int | false
     {
-        $tablePrefix = config('Fediverse')
-            ->tablesPrefix;
-
-        $postsFavouritesCount = $this->db->table($tablePrefix . 'favourites')->select(
-            'post_id as id, COUNT(*) as `favourites_count`'
-        )
+        $postsFavouritesCount = $this->db->table('fediverse_favourites')
+            ->select('post_id as id, COUNT(*) as `favourites_count`')
             ->groupBy('id')
             ->get()
             ->getResultArray();
@@ -639,12 +628,9 @@ class PostModel extends BaseUuidModel
 
     public function resetReblogsCount(): int | false
     {
-        $tablePrefix = config('Fediverse')
-            ->tablesPrefix;
-
-        $postsReblogsCount = $this->select($tablePrefix . 'posts.id, COUNT(*) as `replies_count`')
-            ->join($tablePrefix . 'posts as p2', $tablePrefix . 'posts.id = p2.reblog_of_id')
-            ->groupBy($tablePrefix . 'posts.id')
+        $postsReblogsCount = $this->select('fediverse_posts.id, COUNT(*) as `replies_count`')
+            ->join('fediverse_posts as p2', 'fediverse_posts.id = p2.reblog_of_id')
+            ->groupBy('fediverse_posts.id')
             ->get()
             ->getResultArray();
 
@@ -658,12 +644,9 @@ class PostModel extends BaseUuidModel
 
     public function resetRepliesCount(): int | false
     {
-        $tablePrefix = config('Fediverse')
-            ->tablesPrefix;
-
-        $postsRepliesCount = $this->select($tablePrefix . 'posts.id, COUNT(*) as `replies_count`')
-            ->join($tablePrefix . 'posts as p2', $tablePrefix . 'posts.id = p2.in_reply_to_id')
-            ->groupBy($tablePrefix . 'posts.id')
+        $postsRepliesCount = $this->select('fediverse_posts.id, COUNT(*) as `replies_count`')
+            ->join('fediverse_posts as p2', 'fediverse_posts.id = p2.in_reply_to_id')
+            ->groupBy('fediverse_posts.id')
             ->get()
             ->getResultArray();
 
diff --git a/modules/Fediverse/Models/PreviewCardModel.php b/modules/Fediverse/Models/PreviewCardModel.php
index fb9c028eaa07c3f250f3fc42a4e40ecd9fffdef2..deedbe3b34ce900d9d2125058dbb24ab0ce40ea0 100644
--- a/modules/Fediverse/Models/PreviewCardModel.php
+++ b/modules/Fediverse/Models/PreviewCardModel.php
@@ -11,14 +11,15 @@ declare(strict_types=1);
 namespace Modules\Fediverse\Models;
 
 use CodeIgniter\Database\BaseResult;
+use CodeIgniter\Model;
 use Modules\Fediverse\Entities\PreviewCard;
 
-class PreviewCardModel extends BaseModel
+class PreviewCardModel extends Model
 {
     /**
      * @var string
      */
-    protected $table = 'preview_cards';
+    protected $table = 'fediverse_preview_cards';
 
     /**
      * @var string[]
@@ -75,11 +76,9 @@ class PreviewCardModel extends BaseModel
             config('Fediverse')
                 ->cachePrefix . "post#{$postId}_preview_card";
         if (! ($found = cache($cacheName))) {
-            $tablesPrefix = config('Fediverse')
-                ->tablesPrefix;
             $found = $this->join(
-                $tablesPrefix . 'posts_preview_cards',
-                $tablesPrefix . 'posts_preview_cards.preview_card_id = id',
+                'fediverse_posts_preview_cards',
+                'fediverse_posts_preview_cards.preview_card_id = id',
                 'inner',
             )
                 ->where('post_id', service('uuid') ->fromString($postId) ->getBytes())
diff --git a/modules/Install/Controllers/InstallController.php b/modules/Install/Controllers/InstallController.php
index b417afd48e764dc903ab3ea1d9e3f588c98f0670..54462633e8c8570156160fc1aaa67735c15c0151 100644
--- a/modules/Install/Controllers/InstallController.php
+++ b/modules/Install/Controllers/InstallController.php
@@ -243,8 +243,6 @@ class InstallController extends Controller
     {
         $migrate = Services::migrations();
 
-        $migrate->setNamespace('CodeIgniter\Settings')
-            ->latest();
         $migrate->setNamespace(null)
             ->latest();
     }
diff --git a/modules/Media/Helpers/url_helper.php b/modules/Media/Helpers/url_helper.php
index a3e3c9a3538ca147ac12fdc1b4ae970263adb714..1e96cc96b27a25851131452c0c05365b9c2f13af 100644
--- a/modules/Media/Helpers/url_helper.php
+++ b/modules/Media/Helpers/url_helper.php
@@ -18,7 +18,7 @@ if (! function_exists('media_url')) {
             $relativePath = implode('/', $relativePath);
         }
 
-        $uri = new URI(rtrim((string) config(Media::class)->baseURL, '/') . '/' . ltrim($relativePath));
+        $uri = new URI(rtrim(config(Media::class)->baseURL, '/') . '/' . ltrim($relativePath));
 
         return URI::createURIString(
             $scheme ?? $uri->getScheme(),
diff --git a/modules/PodcastImport/Commands/PodcastImport.php b/modules/PodcastImport/Commands/PodcastImport.php
index e2ebba7472bfd721f181485fa60ab11a48153cb5..fc7ef61eab6566dd1ba8f8ad25430f56e1e25d7b 100644
--- a/modules/PodcastImport/Commands/PodcastImport.php
+++ b/modules/PodcastImport/Commands/PodcastImport.php
@@ -18,6 +18,7 @@ use CodeIgniter\CLI\BaseCommand;
 use CodeIgniter\CLI\CLI;
 use CodeIgniter\I18n\Time;
 use CodeIgniter\Shield\Entities\User;
+use Config\Services;
 use Exception;
 use League\HTMLToMarkdown\HtmlConverter;
 use Modules\Auth\Models\UserModel;
@@ -95,6 +96,9 @@ class PodcastImport extends BaseCommand
 
     public function run(array $params): void
     {
+        // FIXME: getting named routes doesn't work from v4.3 anymore, so loading all routes before importing
+        Services::routes()->loadRoutes();
+
         $this->init();
 
         try {
@@ -503,7 +507,7 @@ class PodcastImport extends BaseCommand
             ->get()
             ->getResultArray();
 
-        return array_map(static function ($element) {
+        return array_map(static function (array $element) {
             return $element['guid'];
         }, $result);
     }
diff --git a/modules/Update/Commands/DatabaseUpdate.php b/modules/Update/Commands/DatabaseUpdate.php
index 54bf5db433a1a47146ffca2137aeadd8ce64cc81..1b7b6b398fd8c153199626ec210750a8634a9355 100644
--- a/modules/Update/Commands/DatabaseUpdate.php
+++ b/modules/Update/Commands/DatabaseUpdate.php
@@ -28,8 +28,6 @@ class DatabaseUpdate extends BaseCommand
     {
         $migrate = Services::migrations();
 
-        $migrate->setNamespace('CodeIgniter\Settings')
-            ->latest();
         $migrate->setNamespace(null)
             ->latest();
     }
diff --git a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php
index b58a75a081b384f5308e3213bed73c3e3fa091b6..a5297b6597be0c806ee56924d0b79ad7b59f2677 100644
--- a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php
+++ b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php
@@ -16,14 +16,14 @@ class AddIsPublishedOnHubsToPodcasts extends BaseMigration
 {
     public function up(): void
     {
-        $prefix = $this->db->getPrefix();
-
-        $createQuery = <<<CODE_SAMPLE
-            ALTER TABLE {$prefix}podcasts
-            ADD COLUMN `is_published_on_hubs` BOOLEAN NOT NULL DEFAULT 0 AFTER `custom_rss`;
-        CODE_SAMPLE;
-
-        $this->db->query($createQuery);
+        $this->forge->addColumn('podcasts', [
+            'is_published_on_hubs' => [
+                'type'    => 'BOOLEAN',
+                'null'    => false,
+                'default' => 0,
+                'after'   => 'custom_rss',
+            ],
+        ]);
     }
 
     public function down(): void
diff --git a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php
index 67a18ab46b2ca93a34ec21c154efbba4c7cf4550..05ee0d0b79d3fce17be4838cf7718e98c7f1f538 100644
--- a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php
+++ b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php
@@ -16,14 +16,14 @@ class AddIsPublishedOnHubsToEpisodes extends BaseMigration
 {
     public function up(): void
     {
-        $prefix = $this->db->getPrefix();
-
-        $createQuery = <<<CODE_SAMPLE
-            ALTER TABLE {$prefix}episodes
-            ADD COLUMN `is_published_on_hubs` BOOLEAN NOT NULL DEFAULT 0 AFTER `custom_rss`;
-        CODE_SAMPLE;
-
-        $this->db->query($createQuery);
+        $this->forge->addColumn('episodes', [
+            'is_published_on_hubs' => [
+                'type'    => 'BOOLEAN',
+                'null'    => false,
+                'default' => 0,
+                'after'   => 'custom_rss',
+            ],
+        ]);
     }
 
     public function down(): void
diff --git a/rector.php b/rector.php
index 875e5d1e8b09bae32ee8c07e52dcfc580a741250..782fdda8ce8fe9c33f48bb897d8c1afa7b6791e0 100644
--- a/rector.php
+++ b/rector.php
@@ -2,7 +2,6 @@
 
 declare(strict_types=1);
 
-use Rector\CodeQuality\Rector\PropertyFetch\ExplicitMethodCallOverMagicGetSetRector;
 use Rector\CodingStyle\Rector\ClassMethod\UnSpreadOperatorRector;
 use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
 use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
@@ -13,7 +12,6 @@ use Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector;
 use Rector\DeadCode\Rector\Stmt\RemoveUnreachableStatementRector;
 use Rector\EarlyReturn\Rector\If_\ChangeAndIfToEarlyReturnRector;
 use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;
-use Rector\EarlyReturn\Rector\If_\ChangeOrIfReturnToEarlyReturnRector;
 use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
 use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
 use Rector\Set\ValueObject\SetList;
@@ -49,11 +47,9 @@ return static function (RectorConfig $rectorConfig): void {
         __DIR__ . '/modules/Admin/Language/*/PersonsTaxonomy.php',
 
         // skip rules from used sets
-        ChangeOrIfReturnToEarlyReturnRector::class,
         ChangeOrIfContinueToMultiContinueRector::class,
         EncapsedStringsToSprintfRector::class,
         UnSpreadOperatorRector::class,
-        ExplicitMethodCallOverMagicGetSetRector::class,
         RemoveExtraParametersRector::class,
         UnwrapFutureCompatibleIfPhpVersionRector::class,
 
diff --git a/themes/cp_admin/_partials/_nav_header.php b/themes/cp_admin/_partials/_nav_header.php
index 21b7c857b8f19ad4399a74d6d9430abec19ec5f4..59c92b27e8e84f0e10951237c4e8bafcf0c54232 100644
--- a/themes/cp_admin/_partials/_nav_header.php
+++ b/themes/cp_admin/_partials/_nav_header.php
@@ -31,9 +31,9 @@ $userPodcasts = get_podcasts_user_can_interact_with(auth()->user()); ?>
 $items = [
     [
         'type'    => 'html',
-        'content' => esc(<<<CODE_SAMPLE
+        'content' => esc(<<<HTML
                         <span class="px-4 my-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$notificationsTitle}</span>
-                        CODE_SAMPLE),
+                        HTML),
     ],
 ];
 
@@ -45,7 +45,7 @@ if ($userPodcasts !== []) {
 
         $items[] = [
             'type'  => 'link',
-            'title' => <<<CODE_SAMPLE
+            'title' => <<<HTML
                             <div class="inline-flex items-center flex-1 text-sm align-middle">
                                 <div class="relative">
                                     <img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" />
@@ -53,7 +53,7 @@ if ($userPodcasts !== []) {
                                 </div>
                                 <span class="max-w-xs truncate">{$userPodcastTitle}</span>
                             </div>
-                        CODE_SAMPLE
+                        HTML
             ,
             'uri' => route_to('notification-list', $userPodcast->id),
         ];
@@ -62,9 +62,9 @@ if ($userPodcasts !== []) {
     $noNotificationsText = lang('Notifications.no_notifications');
     $items[] = [
         'type'    => 'html',
-        'content' => esc(<<<CODE_SAMPLE
+        'content' => esc(<<<HTML
                         <span class="mx-4 my-2 text-sm italic text-center text-skin-muted">{$noNotificationsText}</span>
-                    CODE_SAMPLE),
+                    HTML),
     ];
 }
 ?>
@@ -90,11 +90,11 @@ foreach ($userPodcasts as $userPodcast) {
     $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : '';
     $userPodcastTitle = esc($userPodcast->title);
 
-    $interactButtons .= <<<CODE_SAMPLE
+    $interactButtons .= <<<HTML
                 <button class="inline-flex items-center w-full px-4 py-1 hover:bg-highlight" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
                     <div class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" /><span class="max-w-xs truncate">{$userPodcastTitle}</span>{$checkMark}</div>
                 </button>
-            CODE_SAMPLE;
+            HTML;
 }
 
 $interactAsText = lang('Common.choose_interact');
@@ -126,7 +126,7 @@ if ($userPodcasts !== []) {
     $menuItems = array_merge([
         [
             'type'    => 'html',
-            'content' => esc(<<<CODE_SAMPLE
+            'content' => esc(<<<HTML
                         <nav class="flex flex-col py-2 whitespace-nowrap">
                             <span class="px-4 mb-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$interactAsText}</span>
                             <form action="{$interactAsRoute}" method="POST" class="flex flex-col">
@@ -134,7 +134,7 @@ if ($userPodcasts !== []) {
                                 {$interactButtons}
                             </form>
                         </nav>
-                    CODE_SAMPLE),
+                    HTML),
         ],
         [
             'type' => 'separator',
diff --git a/themes/cp_admin/episode/_card.php b/themes/cp_admin/episode/_card.php
index b03be73e788080538f474a8721c50b35b66c40f9..dc32213e8c22b7c8617288ab4db95be1ccf8ac02 100644
--- a/themes/cp_admin/episode/_card.php
+++ b/themes/cp_admin/episode/_card.php
@@ -66,9 +66,9 @@ if ($episode->published_at === null) {
     $title = lang('Episode.messages.unpublishBeforeDeleteTip');
     $items[] = [
         'type'    => 'html',
-        'content' => esc(<<<CODE_SAMPLE
+        'content' => esc(<<<HTML
                     <span class="inline-flex items-center px-4 py-1 font-semibold text-gray-400 cursor-not-allowed" data-tooltip="bottom" title="{$title}">{$icon}{$label}</span>
-                CODE_SAMPLE),
+                HTML),
     ];
 } ?>
     <DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labelledby="more-dropdown-<?= $episode->id ?>" offsetY="-32" items="<?= esc(json_encode($items)) ?>" />
diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php
index 2e603a59a1bfe85ad14a5d71a27dbd6c066a3c2f..2461c18b482e2886b07e3b7e813c19fbb32fcc45 100644
--- a/themes/cp_admin/episode/list.php
+++ b/themes/cp_admin/episode/list.php
@@ -149,9 +149,9 @@ data_table(
                     $title = lang('Episode.messages.unpublishBeforeDeleteTip');
                     $items[] = [
                         'type'    => 'html',
-                        'content' => esc(<<<CODE_SAMPLE
+                        'content' => esc(<<<HTML
                         <span class="inline-flex items-center px-4 py-1 font-semibold text-gray-400 cursor-not-allowed" data-tooltip="bottom" title="{$title}">{$icon}<span class="ml-2">{$label}</span></span>
-                    CODE_SAMPLE),
+                    HTML),
                     ];
                 }
                 return '<button id="more-dropdown-' . $episode->id . '" type="button" class="inline-flex items-center p-1 rounded-full focus:ring-accent" data-dropdown="button" data-dropdown-target="more-dropdown-' . $episode->id . '-menu" aria-haspopup="true" aria-expanded="false">' .
diff --git a/themes/cp_admin/podcast/notifications.php b/themes/cp_admin/podcast/notifications.php
index 9df9075d7980c05aa5ef2f70d7de93d56ad4b643..c82676fb80eebcd0346af2490cd555001049a0e4 100644
--- a/themes/cp_admin/podcast/notifications.php
+++ b/themes/cp_admin/podcast/notifications.php
@@ -31,15 +31,15 @@
                                 ? ''
                                 : '@' . esc($notification->actor->domain));
 
-                $actorUsernameHtml = <<<CODE_SAMPLE
+                $actorUsernameHtml = <<<HTML
                     <strong class="break-all">{$actorUsername}</strong>
-                    CODE_SAMPLE;
+                    HTML;
 
                 $targetActorUsername = '@' . esc($notification->target_actor->username);
 
-                $targetActorUsernameHtml = <<<CODE_SAMPLE
+                $targetActorUsernameHtml = <<<HTML
                     <strong class="break-all">{$targetActorUsername}</strong>
-                    CODE_SAMPLE;
+                    HTML;
 
                 $notificationTitle = match ($notification->type) {
                     'reply' => lang('Notifications.reply', [
diff --git a/themes/cp_app/_admin_navbar.php b/themes/cp_app/_admin_navbar.php
index 44c7983aab08f29aa0bb3376e94fafa1d675eba5..16d2893333ddcf2b3646d3f75548364cf51f04f0 100644
--- a/themes/cp_app/_admin_navbar.php
+++ b/themes/cp_app/_admin_navbar.php
@@ -26,9 +26,9 @@ $userPodcasts = get_podcasts_user_can_interact_with(auth()->user()); ?>
 $items = [
     [
         'type'    => 'html',
-        'content' => esc(<<<CODE_SAMPLE
+        'content' => esc(<<<HTML
                             <span class="px-4 my-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$notificationsTitle}</span>
-                            CODE_SAMPLE),
+                            HTML),
     ],
 ];
 
@@ -40,7 +40,7 @@ if ($userPodcasts !== []) {
 
         $items[] = [
             'type'  => 'link',
-            'title' => <<<CODE_SAMPLE
+            'title' => <<<HTML
                                 <div class="inline-flex items-center flex-1 text-sm align-middle">
                                     <div class="relative">
                                         <img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" />
@@ -48,7 +48,7 @@ if ($userPodcasts !== []) {
                                     </div>
                                     <span class="max-w-xs truncate">{$userPodcastTitle}</span>
                                 </div>
-                            CODE_SAMPLE
+                            HTML
             ,
             'uri' => route_to('notification-list', $userPodcast->id),
         ];
@@ -57,9 +57,9 @@ if ($userPodcasts !== []) {
     $noNotificationsText = lang('Notifications.no_notifications');
     $items[] = [
         'type'    => 'html',
-        'content' => esc(<<<CODE_SAMPLE
+        'content' => esc(<<<HTML
                             <span class="mx-4 my-2 text-sm italic text-center text-skin-muted">{$noNotificationsText}</span>
-                        CODE_SAMPLE),
+                        HTML),
     ];
 }
 ?>
@@ -90,11 +90,11 @@ foreach ($userPodcasts as $userPodcast) {
         $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-accent-base text-accent-contrast rounded-full') : '';
         $userPodcastTitle = esc($userPodcast->title);
 
-        $interactButtons .= <<<CODE_SAMPLE
+        $interactButtons .= <<<HTML
                        <button class="inline-flex items-center w-full px-4 py-1 hover:bg-highlight" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
                             <div class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->cover->tiny_url}" class="w-6 h-6 mr-2 rounded-full" loading="lazy" /><span class="max-w-xs truncate">{$userPodcastTitle}</span>{$checkMark}</div>
                        </button>
-                    CODE_SAMPLE;
+                    HTML;
     }
 }
 
@@ -127,7 +127,7 @@ if ($userPodcasts !== []) {
     $menuItems = array_merge([
         [
             'type'    => 'html',
-            'content' => esc(<<<CODE_SAMPLE
+            'content' => esc(<<<HTML
                             <nav class="flex flex-col py-2 whitespace-nowrap">
                                 <span class="px-4 mb-2 text-xs font-semibold tracking-wider uppercase text-skin-muted">{$interactAsText}</span>
                                 <form action="{$interactAsRoute}" method="POST" class="flex flex-col">
@@ -135,7 +135,7 @@ if ($userPodcasts !== []) {
                                     {$interactButtons}
                                 </form>
                             </nav>
-                        CODE_SAMPLE),
+                        HTML),
         ],
         [
             'type' => 'separator',