refactor(platforms): move platforms data in code instead of database

refs #457
This commit is contained in:
Yassine Doghri 2024-04-24 14:47:05 +00:00
parent 57e459e187
commit 303a900f66
23 changed files with 11201 additions and 10067 deletions

View File

@ -55,6 +55,7 @@ class Autoload extends AutoloadConfig
'Modules\Install' => ROOTPATH . 'modules/Install/',
'Modules\Media' => ROOTPATH . 'modules/Media/',
'Modules\MediaClipper' => ROOTPATH . 'modules/MediaClipper/',
'Modules\Platforms' => ROOTPATH . 'modules/Platforms/',
'Modules\PodcastImport' => ROOTPATH . 'modules/PodcastImport/',
'Modules\PremiumPodcasts' => ROOTPATH . 'modules/PremiumPodcasts/',
'Modules\Update' => ROOTPATH . 'modules/Update/',

View File

@ -15,7 +15,6 @@ use CodeIgniter\Router\RouteCollection;
$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}');
$routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,128}');
$routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}');
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
$routes->addPlaceholder('postAction', '\bfavourite|\breblog|\breply');
$routes->addPlaceholder('embedTheme', '\blight|\bdark|\blight-transparent|\bdark-transparent');
$routes->addPlaceholder(

View File

@ -28,6 +28,7 @@ class Routing extends BaseRouting
ROOTPATH . 'modules/Auth/Config/Routes.php',
ROOTPATH . 'modules/Fediverse/Config/Routes.php',
ROOTPATH . 'modules/Install/Config/Routes.php',
ROOTPATH . 'modules/Platforms/Config/Routes.php',
ROOTPATH . 'modules/PodcastImport/Config/Routes.php',
ROOTPATH . 'modules/PremiumPodcasts/Config/Routes.php',
];

View File

@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers;
use App\Models\PlatformModel;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\ResponseInterface;
/*
* Provide public access to all platforms so that they can be exported
*/
class PlatformController extends Controller
{
public function index(): ResponseInterface
{
$model = new PlatformModel();
return $this->response->setJSON($model->getPlatforms());
}
}

View File

@ -0,0 +1,152 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class RefactorPlatforms extends Migration
{
public function up(): void
{
$this->forge->addField([
'id' => [
'type' => 'INT',
'unsigned' => true,
'auto_increment' => true,
],
'podcast_id' => [
'type' => 'INT',
'unsigned' => true,
],
'type' => [
'type' => 'ENUM',
'constraint' => ['podcasting', 'social', 'funding'],
'after' => 'podcast_id',
],
'slug' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'link_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
],
'account_id' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'is_visible' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE', 'platforms_podcast_id_foreign');
$this->forge->addUniqueKey(['podcast_id', 'type', 'slug']);
$this->forge->createTable('platforms_temp');
$platformsData = $this->db->table('podcasts_platforms')
->select('podcasts_platforms.*, type')
->join('platforms', 'platforms.slug = podcasts_platforms.platform_slug')
->get()
->getResultArray();
$data = [];
foreach ($platformsData as $platformData) {
$data[] = [
'podcast_id' => $platformData['podcast_id'],
'type' => $platformData['type'],
'slug' => $platformData['platform_slug'],
'link_url' => $platformData['link_url'],
'account_id' => $platformData['account_id'],
'is_visible' => $platformData['is_visible'],
];
}
if ($data !== []) {
$this->db->table('platforms_temp')
->insertBatch($data);
}
$this->forge->dropTable('platforms');
$this->forge->dropTable('podcasts_platforms');
$this->forge->renameTable('platforms_temp', 'platforms');
}
public function down(): void
{
// delete platforms
$this->forge->dropTable('platforms');
// recreate platforms and podcasts_platforms tables
$this->forge->addField([
'slug' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'type' => [
'type' => 'ENUM',
'constraint' => ['podcasting', 'social', 'funding'],
],
'label' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'home_url' => [
'type' => 'VARCHAR',
'constraint' => 255,
],
'submit_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
'null' => true,
],
]);
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()');
$this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()'
);
$this->forge->addPrimaryKey('slug');
$this->forge->createTable('platforms');
$this->forge->addField([
'podcast_id' => [
'type' => 'INT',
'unsigned' => true,
],
'platform_slug' => [
'type' => 'VARCHAR',
'constraint' => 32,
],
'link_url' => [
'type' => 'VARCHAR',
'constraint' => 512,
],
'account_id' => [
'type' => 'VARCHAR',
'constraint' => 128,
'null' => true,
],
'is_visible' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
'is_on_embed' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
],
]);
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('platform_slug', 'platforms', 'slug', 'CASCADE');
$this->forge->createTable('podcasts_platforms');
}
}

View File

@ -20,6 +20,5 @@ class AppSeeder extends Seeder
{
$this->call('CategorySeeder');
$this->call('LanguageSeeder');
$this->call('PlatformSeeder');
}
}

View File

@ -20,7 +20,6 @@ class DevSeeder extends Seeder
{
$this->call('CategorySeeder');
$this->call('LanguageSeeder');
$this->call('PlatformSeeder');
$this->call('DevSuperadminSeeder');
}
}

View File

@ -15,7 +15,6 @@ use App\Models\ActorModel;
use App\Models\CategoryModel;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PlatformModel;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile;
@ -32,6 +31,8 @@ use League\CommonMark\MarkdownConverter;
use Modules\Auth\Models\UserModel;
use Modules\Media\Entities\Image;
use Modules\Media\Models\MediaModel;
use Modules\Platforms\Entities\Platform;
use Modules\Platforms\Models\PlatformModel;
use Modules\PremiumPodcasts\Entities\Subscription;
use Modules\PremiumPodcasts\Models\SubscriptionModel;
use RuntimeException;
@ -528,7 +529,7 @@ class Podcast extends Entity
}
if ($this->podcasting_platforms === null) {
$this->podcasting_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'podcasting');
$this->podcasting_platforms = (new PlatformModel())->getPlatforms($this->id, 'podcasting');
}
return $this->podcasting_platforms;
@ -546,7 +547,7 @@ class Podcast extends Entity
}
if ($this->social_platforms === null) {
$this->social_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'social');
$this->social_platforms = (new PlatformModel())->getPlatforms($this->id, 'social');
}
return $this->social_platforms;
@ -564,7 +565,7 @@ class Podcast extends Entity
}
if ($this->funding_platforms === null) {
$this->funding_platforms = (new PlatformModel())->getPodcastPlatforms($this->id, 'funding');
$this->funding_platforms = (new PlatformModel())->getPlatforms($this->id, 'funding');
}
return $this->funding_platforms;

View File

@ -1,205 +0,0 @@
<?php
declare(strict_types=1);
/**
* Class PlatformModel Model for platforms table in database
*
* @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Models;
use App\Entities\Platform;
use CodeIgniter\Model;
use Config\App;
class PlatformModel extends Model
{
/**
* @var string
*/
protected $table = 'platforms';
/**
* @var string
*/
protected $primaryKey = 'slug';
/**
* @var string[]
*/
protected $allowedFields = ['slug', 'type', 'label', 'home_url', 'submit_url'];
/**
* @var string
*/
protected $returnType = Platform::class;
/**
* @var bool
*/
protected $useSoftDeletes = false;
/**
* @var bool
*/
protected $useTimestamps = false;
/**
* @return Platform[]
*/
public function getPlatforms(): array
{
if (! ($found = cache('platforms'))) {
$baseUrl = rtrim(config(App::class)->baseURL, '/');
$found = $this->select(
"*, CONCAT('{$baseUrl}/assets/images/platforms/',`type`,'/',`slug`,'.svg') as icon",
)->findAll();
cache()
->save('platforms', $found, DECADE);
}
return $found;
}
public function getPlatform(string $slug): ?Platform
{
$cacheName = "platform-{$slug}";
if (! ($found = cache($cacheName))) {
$found = $this->where('slug', $slug)
->first();
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
public function createPlatform(
string $slug,
string $type,
string $label,
string $homeUrl,
string $submitUrl = null
): bool {
$data = [
'slug' => $slug,
'type' => $type,
'label' => $label,
'home_url' => $homeUrl,
'submit_url' => $submitUrl,
];
return $this->insert($data, false);
}
/**
* @return Platform[]
*/
public function getPlatformsWithLinks(int $podcastId, string $platformType): array
{
if (
! ($found = cache("podcast#{$podcastId}_platforms_{$platformType}_withLinks"))
) {
$found = $this->select(
'platforms.*, podcasts_platforms.link_url, podcasts_platforms.account_id, podcasts_platforms.is_visible, podcasts_platforms.is_on_embed',
)
->join(
'podcasts_platforms',
"podcasts_platforms.platform_slug = platforms.slug AND podcasts_platforms.podcast_id = {$podcastId}",
'left',
)
->where('platforms.type', $platformType)
->findAll();
cache()
->save("podcast#{$podcastId}_platforms_{$platformType}_withLinks", $found, DECADE);
}
return $found;
}
/**
* @return Platform[]
*/
public function getPodcastPlatforms(int $podcastId, string $platformType): array
{
$cacheName = "podcast#{$podcastId}_platforms_{$platformType}";
if (! ($found = cache($cacheName))) {
$found = $this->select(
'platforms.*, podcasts_platforms.link_url, podcasts_platforms.account_id, podcasts_platforms.is_visible, podcasts_platforms.is_on_embed',
)
->join('podcasts_platforms', 'podcasts_platforms.platform_slug = platforms.slug')
->where('podcasts_platforms.podcast_id', $podcastId)
->where('platforms.type', $platformType)
->findAll();
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
/**
* @param mixed[] $podcastsPlatformsData
*
* @return int|false Number of rows inserted or FALSE on failure
*/
public function savePodcastPlatforms(
int $podcastId,
string $platformType,
array $podcastsPlatformsData
): int | false {
$this->clearCache($podcastId);
$podcastsPlatformsTable = $this->db->prefixTable('podcasts_platforms');
$platformsTable = $this->db->prefixTable('platforms');
$deleteJoinQuery = <<<SQL
DELETE {$podcastsPlatformsTable}
FROM {$podcastsPlatformsTable}
INNER JOIN {$platformsTable} ON {$platformsTable}.slug = {$podcastsPlatformsTable}.platform_slug
WHERE `podcast_id` = ? AND `type` = ?
SQL;
$this->db->query($deleteJoinQuery, [$podcastId, $platformType]);
if ($podcastsPlatformsData === []) {
// no rows inserted
return 0;
}
return $this->db
->table('podcasts_platforms')
->insertBatch($podcastsPlatformsData);
}
public function removePodcastPlatform(int $podcastId, string $platformSlug): bool | string
{
$this->clearCache($podcastId);
return $this->db->table('podcasts_platforms')
->delete([
'podcast_id' => $podcastId,
'platform_slug' => $platformSlug,
]);
}
public function clearCache(int $podcastId): void
{
cache()->deleteMatching("podcast#{$podcastId}_platforms_*");
// delete localized podcast page cache
cache()
->deleteMatching("page_podcast#{$podcastId}*");
// delete post and episode comments pages cache
cache()
->deleteMatching('page_post*');
cache()
->deleteMatching('page_episode#*');
}
}

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M0,0H24V24H0Z" fill="none" />
<path d="M13.3,13.07a1.91,1.91,0,0,1,.48,0,2.51,2.51,0,0,1,1.88,1.63c.08.26.19.66.26.93l.09.37h.38a5.49,5.49,0,0,0,5.38-5.46c0-4.52-3.77-8.28-9.54-8.28A10.16,10.16,0,0,0,5.9,4.56,10,10,0,0,0,2.23,12.3a10.64,10.64,0,0,0,.28,2.35,10,10,0,0,0,9.72,7.65,11.07,11.07,0,0,0,1.3-.08A10,10,0,0,0,20,18.6l.1-.12-.2-.73-.38.1a14.32,14.32,0,0,1-3.72.48,14.14,14.14,0,0,1-3-.31,2.51,2.51,0,0,1-1.87-1.65,2.51,2.51,0,0,1,.48-2.44A2.5,2.5,0,0,1,13.3,13.07ZM12.23,3.24c5.34,0,8.6,3.42,8.6,7.34a4.55,4.55,0,0,1-4.1,4.5c-.06-.22-.13-.45-.18-.62a15.25,15.25,0,0,0-9.36-9.7A8.79,8.79,0,0,1,12.23,3.24ZM3.17,12.3A9,9,0,0,1,6.3,5.45a14.35,14.35,0,0,1,8.38,7A3.26,3.26,0,0,0,14,12.2a15.86,15.86,0,0,0-3.17-.33,15.65,15.65,0,0,0-7.51,1.94A9.33,9.33,0,0,1,3.17,12.3Zm9.44,6.64a15.9,15.9,0,0,0,3.19.33,16.77,16.77,0,0,0,2.46-.19,9,9,0,0,1-4.6,2.17,13.91,13.91,0,0,1-1.73-2.52A3.83,3.83,0,0,0,12.61,18.94Zm-.07,2.42h-.31a8.82,8.82,0,0,1-4.16-1A14.24,14.24,0,0,1,9.88,16a4.07,4.07,0,0,0,.16.71A15.68,15.68,0,0,0,12.54,21.36Zm-1.83-8a15.6,15.6,0,0,0-3.48,6.55,9,9,0,0,1-3.72-5.1,14.27,14.27,0,0,1,7.29-1.95h.44A3.57,3.57,0,0,0,10.71,13.31Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M0,0H24V24H0Z" fill="none" />
<path d="M3.22,10.08A1.24,1.24,0,0,0,2,11.31V12.7a1.22,1.22,0,0,0,2.44,0h0V11.31A1.17,1.17,0,0,0,3.22,10.08Zm17.56,0a1.23,1.23,0,0,0-1.22,1.23V12.7a1.21,1.21,0,0,0,1.22,1.22A1.22,1.22,0,0,0,22,12.7h0V11.31A1.24,1.24,0,0,0,20.78,10.08ZM7.56,14.2a1.24,1.24,0,0,0-1.23,1.23v1.4a1.23,1.23,0,0,0,2.45,0h0v-1.4A1.17,1.17,0,0,0,7.56,14.2ZM7.56,6A1.24,1.24,0,0,0,6.33,7.24V11.7h0a1.23,1.23,0,0,0,2.45,0h0V7.24A1.17,1.17,0,0,0,7.56,6Zm8.88,0a1.24,1.24,0,0,0-1.22,1.23V8.63a1.23,1.23,0,0,0,2.45,0h0V7.24A1.24,1.24,0,0,0,16.44,6ZM12,2a1.23,1.23,0,0,0-1.22,1.23V4.62a1.22,1.22,0,1,0,2.44,0h0V3.23A1.23,1.23,0,0,0,12,2Zm0,16.16a1.22,1.22,0,0,0-1.22,1.22v1.4a1.22,1.22,0,1,0,2.44,0h0v-1.4A1.26,1.26,0,0,0,12,18.16Zm4.44-7a1.23,1.23,0,0,0-1.22,1.22v4.47a1.23,1.23,0,0,0,2.45,0h0V12.36A1.23,1.23,0,0,0,16.44,11.14ZM13.22,8.41a1.22,1.22,0,0,0-2.44,0h0v7.36h0a1.22,1.22,0,1,0,2.44,0h0V8.41Z" />
</svg>

Before

Width:  |  Height:  |  Size: 991 B

View File

@ -28,17 +28,18 @@ class Button extends Component
public function render(): string
{
$baseClass =
'gap-x-2 flex-shrink-0 inline-flex items-center justify-center font-semibold shadow-xs rounded-full focus:ring-accent';
'gap-x-2 flex-shrink-0 inline-flex items-center justify-center font-semibold rounded-full focus:ring-accent';
$variantClass = [
'default' => 'text-black bg-gray-300 hover:bg-gray-400',
'primary' => 'text-accent-contrast bg-accent-base hover:bg-accent-hover',
'secondary' => 'border-2 border-accent-base text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover',
'success' => 'text-white bg-pine-500 hover:bg-pine-800',
'danger' => 'text-white bg-red-600 hover:bg-red-700',
'warning' => 'text-black bg-yellow-500 hover:bg-yellow-600',
'info' => 'text-white bg-blue-500 hover:bg-blue-600',
'disabled' => 'text-black bg-gray-300 cursor-not-allowed',
'default' => 'shadow-sm text-black bg-gray-300 hover:bg-gray-400',
'primary' => 'shadow-sm text-accent-contrast bg-accent-base hover:bg-accent-hover',
'secondary' => 'shadow-sm border-2 border-accent-base text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover',
'success' => 'shadow-sm text-white bg-pine-500 hover:bg-pine-800',
'danger' => 'shadow-sm text-white bg-red-600 hover:bg-red-700',
'warning' => 'shadow-sm text-black bg-yellow-500 hover:bg-yellow-600',
'info' => 'shadow-sm text-white bg-blue-500 hover:bg-blue-600',
'disabled' => 'shadow-sm text-black bg-gray-300 cursor-not-allowed',
'link' => 'text-accent-base bg-transparent underline hover:no-underline',
];
$sizeClass = [

View File

@ -303,11 +303,8 @@ You may skip this section if you go through the install wizard (go to
# Populates all Languages
php spark db:seed LanguageSeeder
# Populates all podcasts platforms
php spark db:seed PlatformSeeder
# Adds a superadmin with [admin@castopod.local / castopod] credentials
php spark db:seed PlatformSeeder
php spark db:seed DevSuperadminSeeder
```
3. (optionnal) Populate the database with test data:

View File

@ -511,48 +511,6 @@ $routes->group(
});
});
});
$routes->group('platforms', static function ($routes): void {
$routes->get(
'/',
'PodcastPlatformController::platforms/$1/podcasting',
[
'as' => 'platforms-podcasting',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->get(
'social',
'PodcastPlatformController::platforms/$1/social',
[
'as' => 'platforms-social',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->get(
'funding',
'PodcastPlatformController::platforms/$1/funding',
[
'as' => 'platforms-funding',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->post(
'save/(:platformType)',
'PodcastPlatformController::attemptPlatformsUpdate/$1/$2',
[
'as' => 'platforms-save',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->get(
'(:slug)/podcast-platform-remove',
'PodcastPlatformController::removePodcastPlatform/$1/$2',
[
'as' => 'podcast-platform-remove',
'filter' => 'permission:podcast#.manage-platforms',
],
);
});
// Podcast notifications
$routes->group('notifications', static function ($routes): void {
$routes->get('/', 'NotificationController::list/$1', [

View File

@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Modules\PremiumPodcasts\Config;
use CodeIgniter\Router\RouteCollection;
use Modules\Admin\Config\Admin;
$routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding');
/** @var RouteCollection $routes */
// Admin routes for subscriptions
$routes->group(
config(Admin::class)
->gateway,
[
'namespace' => 'Modules\Platforms\Controllers',
],
static function ($routes): void {
$routes->group('podcasts/(:num)/platforms', static function ($routes): void {
$routes->get(
'/',
'PlatformController::platforms/$1/podcasting',
[
'as' => 'platforms-podcasting',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->get(
'social',
'PlatformController::platforms/$1/social',
[
'as' => 'platforms-social',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->get(
'funding',
'PlatformController::platforms/$1/funding',
[
'as' => 'platforms-funding',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->post(
'save/(:platformType)',
'PlatformController::attemptPlatformsUpdate/$1/$2',
[
'as' => 'platforms-save',
'filter' => 'permission:podcast#.manage-platforms',
],
);
$routes->get(
'(:platformType)/(:slug)/podcast-platform-remove',
'PlatformController::removePlatform/$1/$2/$3',
[
'as' => 'podcast-platform-remove',
'filter' => 'permission:podcast#.manage-platforms',
],
);
});
}
);

View File

@ -8,18 +8,19 @@ declare(strict_types=1);
* @link https://castopod.org/
*/
namespace Modules\Admin\Controllers;
namespace Modules\Platforms\Controllers;
use App\Entities\Podcast;
use App\Models\PlatformModel;
use App\Models\PodcastModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services;
use Modules\Admin\Controllers\BaseController;
use Modules\Platforms\Models\PlatformModel;
class PodcastPlatformController extends BaseController
class PlatformController extends BaseController
{
protected ?Podcast $podcast;
protected Podcast $podcast;
public function _remap(string $method, string ...$params): mixed
{
@ -28,18 +29,20 @@ class PodcastPlatformController extends BaseController
}
if (
($this->podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast
! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast
) {
unset($params[0]);
return $this->{$method}(...$params);
throw PageNotFoundException::forPageNotFound();
}
throw PageNotFoundException::forPageNotFound();
$this->podcast = $podcast;
unset($params[0]);
return $this->{$method}(...$params);
}
public function index(): string
{
return view('podcast/platforms\dashboard');
return view('podcast/platforms/dashboard');
}
public function platforms(string $platformType): string
@ -49,7 +52,7 @@ class PodcastPlatformController extends BaseController
$data = [
'podcast' => $this->podcast,
'platformType' => $platformType,
'platforms' => (new PlatformModel())->getPlatformsWithLinks($this->podcast->id, $platformType),
'platforms' => (new PlatformModel())->getPlatformsWithData($this->podcast->id, $platformType),
];
replace_breadcrumb_params([
@ -64,8 +67,7 @@ class PodcastPlatformController extends BaseController
$platformModel = new PlatformModel();
$validation = Services::validation();
$podcastsPlatformsData = [];
$platformsData = [];
foreach (
$this->request->getPost('platforms')
as $platformSlug => $podcastPlatform
@ -80,30 +82,27 @@ class PodcastPlatformController extends BaseController
}
$podcastPlatformAccountId = trim((string) $podcastPlatform['account_id']);
$podcastsPlatformsData[] = [
'platform_slug' => $platformSlug,
'podcast_id' => $this->podcast->id,
'link_url' => $podcastPlatformUrl,
'account_id' => $podcastPlatformAccountId === '' ? null : $podcastPlatformAccountId,
'is_visible' => array_key_exists('visible', $podcastPlatform) &&
$platformsData[] = [
'podcast_id' => $this->podcast->id,
'type' => $platformType,
'slug' => $platformSlug,
'link_url' => $podcastPlatformUrl,
'account_id' => $podcastPlatformAccountId === '' ? null : $podcastPlatformAccountId,
'is_visible' => array_key_exists('visible', $podcastPlatform) &&
$podcastPlatform['visible'] === 'yes',
'is_on_embed' => array_key_exists(
'on_embed',
$podcastPlatform
) && $podcastPlatform['on_embed'] === 'yes',
];
}
$platformModel->savePodcastPlatforms($this->podcast->id, $platformType, $podcastsPlatformsData);
$platformModel->savePlatforms($this->podcast->id, $platformType, $platformsData);
return redirect()
->back()
->with('message', lang('Platforms.messages.updateSuccess'));
}
public function removePodcastPlatform(string $platformSlug): RedirectResponse
public function removePlatform(string $platformType, string $platformSlug): RedirectResponse
{
(new PlatformModel())->removePodcastPlatform($this->podcast->id, $platformSlug);
(new PlatformModel())->removePlatform($this->podcast->id, $platformType, $platformSlug);
return redirect()
->back()

View File

@ -8,20 +8,20 @@ declare(strict_types=1);
* @link https://castopod.org/
*/
namespace App\Entities;
namespace Modules\Platforms\Entities;
use CodeIgniter\Entity\Entity;
/**
* @property int $podcast_id
* @property string $slug
* @property string $type
* @property string $label
* @property string $link_url
* @property string|null $account_id
* @property bool $is_visible
* @property string $home_url
* @property string|null $submit_url
* @property string|null $link_url
* @property string|null $account_id
* @property bool|null $is_visible
* @property bool|null $is_on_embed
*/
class Platform extends Entity
{
@ -29,14 +29,14 @@ class Platform extends Entity
* @var array<string, string>
*/
protected $casts = [
'slug' => 'string',
'type' => 'string',
'label' => 'string',
'home_url' => 'string',
'submit_url' => '?string',
'link_url' => '?string',
'account_id' => '?string',
'is_visible' => '?boolean',
'is_on_embed' => '?boolean',
'podcast_id' => 'int',
'slug' => 'string',
'type' => 'string',
'label' => 'string',
'link_url' => 'string',
'account_id' => '?string',
'is_visible' => 'boolean',
'home_url' => 'string',
'submit_url' => '?string',
];
}

View File

@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
/**
* Class PlatformModel Model for platforms table in database
*
* @copyright 2024 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace Modules\Platforms\Models;
use CodeIgniter\Model;
use Modules\Platforms\Entities\Platform;
use Modules\Platforms\Platforms;
class PlatformModel extends Model
{
/**
* @var string
*/
protected $table = 'platforms';
/**
* @var string
*/
protected $primaryKey = 'id';
/**
* @var string[]
*/
protected $allowedFields = ['podcast_id', 'type', 'slug', 'link_url', 'account_id', 'is_visible'];
/**
* @var string
*/
protected $returnType = Platform::class;
/**
* @var bool
*/
protected $useSoftDeletes = false;
/**
* @var bool
*/
protected $useTimestamps = false;
/**
* @return Platform[]
*/
public function getPlatformsWithData(int $podcastId, string $platformType): array
{
$cacheName = "podcast#{$podcastId}_platforms_{$platformType}_withData";
if (! ($found = cache($cacheName))) {
$platforms = new Platforms();
$found = $this->getPlatforms($podcastId, $platformType);
$platformsData = $platforms->getPlatformsByType($platformType);
$knownSlugs = [];
foreach ($found as $podcastPlatform) {
$knownSlugs[] = $podcastPlatform->slug;
}
foreach ($platformsData as $slug => $platform) {
if (! in_array($slug, $knownSlugs)) {
$found[] = new Platform([
'podcast_id' => $podcastId,
'slug' => $slug,
'type' => $platformType,
'label' => $platform['label'],
'home_url' => $platform['home_url'],
'submit_url' => $platform['submit_url'],
'link_url' => '',
'account_id' => null,
'is_visible' => false,
]);
}
}
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
/**
* @return Platform[]
*/
public function getPlatforms(int $podcastId, string $platformType): array
{
$cacheName = "podcast#{$podcastId}_platforms_{$platformType}";
if (! ($found = cache($cacheName))) {
$platforms = new Platforms();
/** @var Platform[] $found */
$found = $this
->where('podcast_id', $podcastId)
->where('type', $platformType)
->orderBy('slug')
->findAll();
foreach ($found as $platform) {
$platformData = $platforms->findPlatformBySlug($platformType, $platform->slug);
if ($platformData === null) {
// delete platform, it does not correspond to any existing one
$this->delete($platform->id);
continue;
}
$platform->type = $platformType;
$platform->label = $platformData['label'];
$platform->home_url = $platformData['home_url'];
$platform->submit_url = $platformData['submit_url'];
}
cache()
->save($cacheName, $found, DECADE);
}
return $found;
}
/**
* @return int|false Number of rows inserted or FALSE on failure
*/
public function savePlatforms(int $podcastId, string $platformType, array $data): int | false
{
$this->clearCache($podcastId);
$platforms = new Platforms();
$platformsData = $platforms->getPlatformsByType($platformType);
$this->builder()
->whereIn('slug', array_keys($platformsData))
->delete();
if ($data === []) {
// no rows inserted
return 0;
}
return $this->insertBatch($data);
}
public function removePlatform(int $podcastId, string $platformType, string $platformSlug): bool | string
{
$this->clearCache($podcastId);
return $this->builder()
->delete([
'podcast_id' => $podcastId,
'type' => $platformType,
'slug' => $platformSlug,
]);
}
public function clearCache(int $podcastId): void
{
cache()->deleteMatching("podcast#{$podcastId}_platforms_*");
// delete localized podcast page cache
cache()
->deleteMatching("page_podcast#{$podcastId}*");
// delete post and episode comments pages cache
cache()
->deleteMatching('page_post*');
cache()
->deleteMatching('page_episode#*');
}
}

View File

@ -8,11 +8,9 @@ use AdAures\PodcastPersonsTaxonomy\ReversedTaxonomy;
use App\Entities\Episode;
use App\Entities\Location;
use App\Entities\Person;
use App\Entities\Platform;
use App\Entities\Podcast;
use App\Models\EpisodeModel;
use App\Models\PersonModel;
use App\Models\PlatformModel;
use App\Models\PodcastModel;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
@ -23,6 +21,8 @@ use Exception;
use League\HTMLToMarkdown\HtmlConverter;
use Modules\Auth\Config\AuthGroups;
use Modules\Auth\Models\UserModel;
use Modules\Platforms\Models\PlatformModel;
use Modules\Platforms\Platforms;
use Modules\PodcastImport\Entities\PodcastImportTask;
use Modules\PodcastImport\Entities\TaskStatus;
use PodcastFeed\PodcastFeed;
@ -392,27 +392,32 @@ class PodcastImport extends BaseCommand
],
];
$platforms = new Platforms();
$platformModel = new PlatformModel();
foreach ($platformTypes as $platformType) {
$podcastsPlatformsData = [];
$platformsData = [];
$currPlatformStep = 1; // for progress
CLI::write($platformType['name'] . ' - ' . $platformType['count'] . ' elements');
foreach ($platformType['elements'] as $platform) {
CLI::showProgress($currPlatformStep++, $platformType['count']);
$platformLabel = $platform->getAttribute('platform');
$platformSlug = slugify((string) $platformLabel);
if ($platformModel->getPlatform($platformSlug) instanceof Platform) {
$podcastsPlatformsData[] = [
'platform_slug' => $platformSlug,
'podcast_id' => $this->podcast->id,
'link_url' => $platform->getAttribute($platformType['account_url_key']),
'account_id' => $platform->getAttribute($platformType['account_id_key']),
'is_visible' => false,
];
$platformSlug = $platform->getAttribute('platform');
$platformData = $platforms->findPlatformBySlug($platformType['name'], $platformSlug);
if ($platformData === null) {
continue;
}
$platformsData[] = [
'podcast_id' => $this->podcast->id,
'type' => $platformType['name'],
'slug' => $platformSlug,
'link_url' => $platform->getAttribute($platformType['account_url_key']),
'account_id' => $platform->getAttribute($platformType['account_id_key']),
'is_visible' => false,
];
}
$platformModel->savePodcastPlatforms($this->podcast->id, $platformType['name'], $podcastsPlatformsData);
$platformModel->savePlatforms($this->podcast->id, $platformType['name'], $platformsData);
CLI::showProgress(false);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -117,6 +117,7 @@ module.exports = {
cards: "repeat(auto-fill, minmax(14rem, 1fr))",
latestEpisodes: "repeat(5, 1fr)",
colorButtons: "repeat(auto-fill, minmax(4rem, 1fr))",
platforms: "repeat(auto-fill, minmax(18rem, 1fr))",
},
gridTemplateRows: {
admin: "40px 1fr",

View File

@ -14,9 +14,10 @@
<?= $this->section('content') ?>
<form id="platforms-form" action="<?= route_to('platforms-save', $podcast->id, $platformType) ?>" method="POST" class="flex flex-col max-w-md gap-y-8">
<form id="platforms-form" action="<?= route_to('platforms-save', $podcast->id, $platformType) ?>" method="POST" class="grid w-full gap-4 lg:gap-8 grid-cols-platforms">
<?= csrf_field() ?>
<?php foreach ($platforms as $platform): ?>
<div class="relative flex-col items-start p-4 rounded-lg bg-elevated border-3 <?= $platform->link_url ? 'border-accent-base' : 'border-subtle' ?>">
@ -24,7 +25,8 @@
route_to(
'podcast-platform-remove',
$podcast->id,
esc($platform->slug),
$platform->type,
$platform->slug,
),
icon('delete-bin', 'mx-auto'),
[
@ -36,7 +38,7 @@
],
)
: '' ?>
<div class="flex items-center gap-x-4">
<div class="flex items-center gap-x-2">
<?= icon(
esc($platform->slug),
'text-skin-muted text-4xl',
@ -45,29 +47,15 @@
<h2 class="text-xl font-semibold"><?= $platform->label ?></h2>
</div>
<div class="flex flex-col flex-1 mt-4">
<div class="inline-flex ml-12 gap-x-2">
<?= anchor($platform->home_url, icon('external-link', 'mx-auto') . lang('Platforms.website'), [
'class' => 'gap-x-1 flex-shrink-0 inline-flex items-center justify-center font-semibold shadow-xs rounded-full focus:ring-accent px-2 py-1 text-sm border-2 border-accent-base text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover',
'target' => '_blank',
'rel' => 'noopener noreferrer',
'data-tooltip' => 'bottom',
'title' => lang('Platforms.home_url', [
<div class="inline-flex ml-8 -mt-6 gap-x-1">
<Button uri="<?= $platform->home_url ?>" variant="link" size="small" target="_blank" rel="noopener noreferrer" title="<?= lang('Platforms.home_url', [
'platformName' => $platform->label,
]) ?>" data-tooltip="bottom"><?= lang('Platforms.website') ?></Button>
<?php if ($platform->submit_url !== null): ?>
<Button uri="<?= $platform->submit_url ?>" variant="link" size="small" target="_blank" rel="noopener noreferrer" title="<?= lang('Platforms.submit_url', [
'platformName' => $platform->label,
]),
]) ?>
<?= $platform->submit_url ? anchor(
$platform->submit_url,
icon('add') . lang('Platforms.register'),
[
'class' => 'gap-x-1 flex-shrink-0 inline-flex items-center justify-center font-semibold shadow-xs rounded-full focus:ring-accent px-2 py-1 text-sm border-2 border-accent-base text-accent-base bg-white hover:border-accent-hover hover:text-accent-hover',
'target' => '_blank',
'rel' => 'noopener noreferrer',
'data-tooltip' => 'bottom',
'title' => lang('Platforms.submit_url', [
'platformName' => $platform->label,
]),
]
) : '' ?>
]) ?>" data-tooltip="bottom"><?= lang('Platforms.register') ?></Button>
<?php endif; ?>
</div>
<fieldset>
<Forms.Field