From 7ff1dbe9030768074b2fe7c7f570bfb9e7336f62 Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Sat, 26 Aug 2023 13:03:01 +0000
Subject: [PATCH] fix: remove fediverse prefix to prevent migration error +
 load routes during podcast import

refactor migration queries to use forge functions
---
 app/Config/View.php                           |   6 +-
 app/Controllers/EpisodeController.php         |   2 +-
 .../2021-05-30-101500_add_podcasts.php        |   2 +-
 .../2021-06-05-170000_add_episodes.php        |   6 +-
 ...2021-08-12-150000_add_episode_comments.php |   5 +-
 .../2021-08-12-160000_add_likes.php           |   5 +-
 .../2021-12-25-150000_add_credits_view.php    |   4 +-
 ...2-02-23-100000_add_episode_id_to_posts.php |  28 ++--
 ...2-03-09-113000_add_created_by_to_posts.php |  28 ++--
 ...12-010000_add_full_text_search_indexes.php |  20 +--
 app/Entities/Clip/BaseClip.php                |   3 +-
 app/Entities/Episode.php                      |   3 +-
 app/Entities/EpisodeComment.php               |   3 +-
 app/Entities/Podcast.php                      |   3 +-
 app/Filters/AllowCorsFilter.php               |   6 +
 app/Helpers/components_helper.php             |   6 +-
 app/Libraries/RouteCollection.php             | 153 +++++++++++++++++-
 .../ViewComponents/ComponentRenderer.php      |   4 +-
 app/Libraries/Vite/Vite.php                   |   8 +-
 app/Models/CategoryModel.php                  |   9 +-
 app/Models/EpisodeCommentModel.php            |   2 +-
 app/Models/EpisodeModel.php                   |  16 +-
 app/Models/PersonModel.php                    |   2 +-
 app/Models/PlatformModel.php                  |   4 +-
 app/Models/PodcastModel.php                   |  17 +-
 app/Models/PostModel.php                      |   4 +-
 modules/Auth/Commands/RolesDoc.php            |   4 +-
 .../Controllers/ContributorController.php     |   6 +-
 modules/Auth/Controllers/UserController.php   |   4 +-
 modules/Fediverse/Config/Fediverse.php        |   2 -
 .../Fediverse/Controllers/ActorController.php |   9 +-
 .../2018-01-01-010000_add_actors.php          |   4 +-
 .../2018-01-01-020000_add_posts.php           |  13 +-
 .../2018-01-01-100000_add_activities.php      |  13 +-
 .../2018-01-01-100000_add_favourites.php      |  11 +-
 .../2018-01-01-100000_add_follows.php         |  11 +-
 .../2018-01-01-100000_add_preview_cards.php   |   4 +-
 ...8-01-01-110000_add_posts_preview_cards.php |  11 +-
 .../2018-01-01-120000_add_blocked_domains.php |   4 +-
 .../2018-01-01-130000_add_notifications.php   |  15 +-
 .../Fediverse/Helpers/fediverse_helper.php    |   4 +-
 modules/Fediverse/Models/ActivityModel.php    |   5 +-
 modules/Fediverse/Models/ActorModel.php       |  39 ++---
 modules/Fediverse/Models/BaseModel.php        |  26 ---
 modules/Fediverse/Models/BaseUuidModel.php    |  20 ---
 .../Fediverse/Models/BlockedDomainModel.php   |   5 +-
 modules/Fediverse/Models/FavouriteModel.php   |   5 +-
 modules/Fediverse/Models/FollowModel.php      |   5 +-
 .../Fediverse/Models/NotificationModel.php    |   5 +-
 modules/Fediverse/Models/PostModel.php        |  53 +++---
 modules/Fediverse/Models/PreviewCardModel.php |  11 +-
 .../Install/Controllers/InstallController.php |   2 -
 modules/Media/Helpers/url_helper.php          |   2 +-
 .../PodcastImport/Commands/PodcastImport.php  |   6 +-
 modules/Update/Commands/DatabaseUpdate.php    |   2 -
 ...0_add_is_published_on_hubs_to_podcasts.php |  16 +-
 ...0_add_is_published_on_hubs_to_episodes.php |  16 +-
 rector.php                                    |   4 -
 themes/cp_admin/_partials/_nav_header.php     |  20 +--
 themes/cp_admin/episode/_card.php             |   4 +-
 themes/cp_admin/episode/list.php              |   4 +-
 themes/cp_admin/podcast/notifications.php     |   8 +-
 themes/cp_app/_admin_navbar.php               |  20 +--
 63 files changed, 387 insertions(+), 355 deletions(-)
 delete mode 100644 modules/Fediverse/Models/BaseModel.php
 delete mode 100644 modules/Fediverse/Models/BaseUuidModel.php

diff --git a/app/Config/View.php b/app/Config/View.php
index 104cdd8d86..ae4d747564 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 71f7cc9246..46a4f860a5 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 006d7224a8..fbf6916b4d 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 22641eab79..9aa57ba8c8 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 4c1869ceb3..54202baa6f 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 830de1f19e..53917ac0c4 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 bef51fa5e7..9955e3ca24 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 c22cb5f8ad..ecceb7e1cc 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 ccd6de671a..93ff2022ac 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 d505153017..310ce8f31f 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 3528bfe80b..d8eab7bc03 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 c7af02d3f0..c51e1e6dee 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 84e2025985..5b10d13333 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 0ab77601a5..d57a1463c8 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 7edc2b6e96..96d6409bf4 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 176e75c7ae..fdc77f21a4 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 87576d341c..75ea475799 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 7eb0984d21..f9e916c045 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 57d7ddc040..64b4202d99 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 50ab67fbe8..235139d9fc 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 862177c597..bbceb48f5a 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 aceddab840..339acfb54c 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 83dad16233..50f1fa4f7f 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 dc9475bf0e..14219f809b 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 0e5b732c38..f64d791567 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 32639a9fad..ae409903c5 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 06e263ae64..6e15583e1a 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 2eb6acc736..d976bb413c 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 1a5d059e5d..9da5d0ed91 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 f40fec3757..835567e024 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 6048e7fefd..52f35e36d3 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 c2fa15d1ba..ddf5228c91 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 1f04ef2abb..903f8aec96 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 2baa67c9a4..18b94cdf24 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 33aaa8989e..75ec8f2175 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 9704a7a84e..0b0273ba02 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 9f14318453..f0359070dc 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 d6719bcc4d..2bd9d9ba30 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 df4d15b27a..044a0e0b99 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 78b0ab2455..df979192d0 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 ee1d015e9a..53f662f231 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 86b0c5dacf..cf9181fe03 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 3b35583c26..db07c0bea2 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 685f1776ec..0000000000
--- 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 e2f80ea90d..0000000000
--- 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 933f6e522f..e8e4f514e2 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 3188dd2d56..72afc5a0bf 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 cf0cdcee70..e866ba69ac 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 24732d7d0d..53d4f0eb18 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 7a07ea5b43..c472a61bf9 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 fb9c028eaa..deedbe3b34 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 b417afd48e..54462633e8 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 a3e3c9a353..1e96cc96b2 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 e2ebba7472..fc7ef61eab 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 54bf5db433..1b7b6b398f 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 b58a75a081..a5297b6597 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 67a18ab46b..05ee0d0b79 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 875e5d1e8b..782fdda8ce 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 21b7c857b8..59c92b27e8 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 b03be73e78..dc32213e8c 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 2e603a59a1..2461c18b48 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 9df9075d79..c82676fb80 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 44c7983aab..16d2893333 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',
-- 
GitLab