Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • adaures/castopod
  • mkljczk/castopod-host
  • spaetz/castopod-host
  • PatrykMis/castopod
  • jonas/castopod
  • ajeremias/castopod
  • misuzu/castopod
  • KrzysztofDomanczyk/castopod
  • Behel/castopod
  • nebulon/castopod
  • ewen/castopod
  • NeoluxConsulting/castopod
  • nateritter/castopod-og
  • prcutler/castopod
14 results
Show changes
Showing
with 1175 additions and 316 deletions
...@@ -3,36 +3,58 @@ ...@@ -3,36 +3,58 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* @copyright 2020 Podlibre * @copyright 2022 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Controllers; namespace App\Controllers;
use App\Entities\Podcast;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\Controller; use CodeIgniter\Controller;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\ResponseInterface;
use Exception; use Exception;
use Opawg\UserAgentsPhp\UserAgentsRSS; use Modules\PremiumPodcasts\Entities\Subscription;
use Modules\PremiumPodcasts\Models\SubscriptionModel;
use Opawg\UserAgentsV2Php\UserAgentsRSS;
class FeedController extends Controller class FeedController extends Controller
{ {
public function index(string $podcastName): ResponseInterface /**
{ * Instance of the main Request object.
helper('rss'); *
* @var IncomingRequest
*/
protected $request;
$podcast = (new PodcastModel())->where('name', $podcastName) public function index(string $podcastHandle): ResponseInterface
{
$podcast = (new PodcastModel())->where('handle', $podcastHandle)
->first(); ->first();
if (! $podcast) { if (! $podcast instanceof Podcast) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
// 301 redirect to new feed?
$redirectToNewFeed = service('settings')
->get('Podcast.redirect_to_new_feed', 'podcast:' . $podcast->id);
if ($redirectToNewFeed && $podcast->new_feed_url !== null && filter_var(
$podcast->new_feed_url,
FILTER_VALIDATE_URL,
) && $podcast->new_feed_url !== current_url()) {
return redirect()->to($podcast->new_feed_url, 301);
}
helper(['rss', 'premium_podcasts', 'misc']);
$service = null; $service = null;
try { try {
$service = UserAgentsRSS::find($_SERVER['HTTP_USER_AGENT']); $service = UserAgentsRSS::find(service('superglobals')->server('HTTP_USER_AGENT'));
} catch (Exception $exception) { } catch (Exception $exception) {
// If things go wrong the show must go on and the user must be able to download the file // If things go wrong the show must go on and the user must be able to download the file
log_message('critical', $exception->getMessage()); log_message('critical', $exception->getMessage());
...@@ -43,11 +65,24 @@ class FeedController extends Controller ...@@ -43,11 +65,24 @@ class FeedController extends Controller
$serviceSlug = $service['slug']; $serviceSlug = $service['slug'];
} }
$cacheName = $subscription = null;
"podcast#{$podcast->id}_feed" . ($service ? "_{$serviceSlug}" : ''); $token = $this->request->getGet('token');
if ($token) {
$subscription = (new SubscriptionModel())->validateSubscription($podcastHandle, $token);
}
$cacheName = implode(
'_',
array_filter([
"podcast#{$podcast->id}",
'feed',
$service ? $serviceSlug : null,
$subscription instanceof Subscription ? "subscription#{$subscription->id}" : null,
]),
);
if (! ($found = cache($cacheName))) { if (! ($found = cache($cacheName))) {
$found = get_rss_feed($podcast, $serviceSlug); $found = get_rss_feed($podcast, $serviceSlug, $subscription, $token);
// The page cache is set to expire after next episode publication or a decade by default so it is deleted manually upon podcast update // The page cache is set to expire after next episode publication or a decade by default so it is deleted manually upon podcast update
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode( $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
...@@ -55,13 +90,7 @@ class FeedController extends Controller ...@@ -55,13 +90,7 @@ class FeedController extends Controller
); );
cache() cache()
->save( ->save($cacheName, $found, $secondsToNextUnpublishedEpisode ?: DECADE);
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
);
} }
return $this->response->setXML($found); return $this->response->setXML($found);
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
...@@ -13,32 +13,71 @@ namespace App\Controllers; ...@@ -13,32 +13,71 @@ namespace App\Controllers;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RedirectResponse;
use Config\Services; use CodeIgniter\HTTP\ResponseInterface;
use mysqli_sql_exception; use Modules\Media\FileManagers\FileManagerInterface;
class HomeController extends BaseController class HomeController extends BaseController
{ {
public function index(): RedirectResponse | string public function index(): RedirectResponse | string
{ {
try { $sortOptions = ['activity', 'created_desc', 'created_asc'];
$allPodcasts = (new PodcastModel())->findAll(); $sortBy = in_array($this->request->getGet('sort'), $sortOptions, true) ? $this->request->getGet(
} catch (mysqli_sql_exception | DatabaseException) { 'sort',
// An error was caught when retrieving the podcasts from the database. ) : 'activity';
// Redirecting to install page because it is likely that Castopod Host has not been installed yet.
// NB: as base_url wouldn't have been defined here, redirect to install wizard manually $allPodcasts = (new PodcastModel())->getAllPodcasts($sortBy);
$route = Services::routes()->reverseRoute('install');
return redirect()->to(rtrim(host_url(), '/') . $route);
}
// check if there's only one podcast to redirect user to it // check if there's only one podcast to redirect user to it
if (count($allPodcasts) === 1) { if (count($allPodcasts) === 1) {
return redirect()->route('podcast-activity', [$allPodcasts[0]->name]); return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]);
} }
set_home_metatags();
// default behavior: list all podcasts on home page // default behavior: list all podcasts on home page
$data = [ $data = [
'podcasts' => $allPodcasts, 'podcasts' => $allPodcasts,
'sortBy' => $sortBy,
]; ];
return view('home', $data); return view('home', $data);
} }
public function health(): ResponseInterface
{
$errors = [];
try {
db_connect();
} catch (DatabaseException) {
$errors[] = 'Unable to connect to the database.';
}
// --- Can Castopod connect to the cache handler
if (config('Cache')->handler !== 'dummy' && cache()->getCacheInfo() === null) {
$errors[] = 'Unable connect to the cache handler.';
}
// --- Can Castopod write to storage?
/** @var FileManagerInterface $fileManager */
$fileManager = service('file_manager', false);
if (! $fileManager->isHealthy()) {
$errors[] = 'Problem with file manager.';
}
if ($errors !== []) {
return $this->response->setStatusCode(503)
->setJSON([
'code' => 503,
'errors' => $errors,
]);
}
return $this->response->setStatusCode(200)
->setJSON([
'code' => 200,
'message' => '✨ All good!',
]);
}
} }
<?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\EpisodeModel;
use CodeIgniter\HTTP\ResponseInterface;
class MapController extends BaseController
{
public function index(): string
{
$cacheName = implode(
'_',
array_filter([
'page',
'map',
service('request')
->getLocale(),
auth()
->loggedIn() ? 'authenticated' : null,
]),
);
if (! ($found = cache($cacheName))) {
return view('pages/map', [], [
'cache' => DECADE,
'cache_name' => $cacheName,
]);
}
return $found;
}
public function getEpisodesMarkers(): ResponseInterface
{
$cacheName = 'episodes_markers';
if (! ($found = cache($cacheName))) {
$episodes = (new EpisodeModel())
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->where('location_geo is not', null)
->findAll();
$found = [];
foreach ($episodes as $episode) {
$found[] = [
'latitude' => $episode->location->latitude,
'longitude' => $episode->location->longitude,
'location_name' => esc($episode->location->name),
'location_url' => $episode->location->url,
'episode_link' => $episode->link,
'podcast_link' => $episode->podcast->link,
'cover_url' => $episode->cover->thumbnail_url,
'podcast_title' => esc($episode->podcast->title),
'episode_title' => esc($episode->title),
];
}
// The page cache is set to a decade so it is deleted manually upon episode update
cache()
->save($cacheName, $found, DECADE);
}
return $this->response->setJSON($found);
}
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
...@@ -20,13 +20,12 @@ class PageController extends BaseController ...@@ -20,13 +20,12 @@ class PageController extends BaseController
public function _remap(string $method, string ...$params): mixed public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if ($params === []) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if ( $page = (new PageModel())->where('slug', $params[0])->first();
($page = (new PageModel())->where('slug', $params[0])->first()) === null if (! $page instanceof Page) {
) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
...@@ -37,13 +36,25 @@ class PageController extends BaseController ...@@ -37,13 +36,25 @@ class PageController extends BaseController
public function index(): string public function index(): string
{ {
$cacheName = "page-{$this->page->slug}"; $cacheName = implode(
'_',
array_filter([
'page',
$this->page->slug,
service('request')
->getLocale(),
auth()
->loggedIn() ? 'authenticated' : null,
]),
);
if (! ($found = cache($cacheName))) { if (! ($found = cache($cacheName))) {
set_page_metatags($this->page);
$data = [ $data = [
'page' => $this->page, 'page' => $this->page,
]; ];
$found = view('page', $data); $found = view('pages/page', $data);
// The page cache is set to a decade so it is deleted manually upon page update // The page cache is set to a decade so it is deleted manually upon page update
cache() cache()
......
<?php
declare(strict_types=1);
/**
* @copyright 2020 Podlibre
* @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());
}
}
...@@ -3,19 +3,24 @@ ...@@ -3,19 +3,24 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Controllers; namespace App\Controllers;
use Analytics\AnalyticsTrait;
use App\Entities\Podcast; use App\Entities\Podcast;
use App\Libraries\PodcastActor;
use App\Libraries\PodcastEpisode;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\NoteModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\PostModel;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\ResponseInterface;
use Modules\Analytics\AnalyticsTrait;
use Modules\Fediverse\Objects\OrderedCollectionObject;
use Modules\Fediverse\Objects\OrderedCollectionPage;
class PodcastController extends BaseController class PodcastController extends BaseController
{ {
...@@ -25,12 +30,12 @@ class PodcastController extends BaseController ...@@ -25,12 +30,12 @@ class PodcastController extends BaseController
public function _remap(string $method, string ...$params): mixed public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) === 0) { if ($params === []) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
if ( if (
($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null ! ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) instanceof Podcast
) { ) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
...@@ -38,15 +43,22 @@ class PodcastController extends BaseController ...@@ -38,15 +43,22 @@ class PodcastController extends BaseController
$this->podcast = $podcast; $this->podcast = $podcast;
unset($params[0]); unset($params[0]);
return $this->{$method}(...$params); return $this->{$method}(...$params);
} }
public function podcastActor(): ResponseInterface
{
$podcastActor = new PodcastActor($this->podcast);
return $this->response
->setContentType('application/activity+json')
->setBody($podcastActor->toJSON());
}
public function activity(): string public function activity(): string
{ {
// Prevent analytics hit when authenticated $this->registerPodcastWebpageHit($this->podcast->id);
if (! can_user_interact()) {
$this->registerPodcastWebpageHit($this->podcast->id);
}
$cacheName = implode( $cacheName = implode(
'_', '_',
...@@ -56,20 +68,24 @@ class PodcastController extends BaseController ...@@ -56,20 +68,24 @@ class PodcastController extends BaseController
'activity', 'activity',
service('request') service('request')
->getLocale(), ->getLocale(),
can_user_interact() ? '_authenticated' : null, is_unlocked($this->podcast->handle) ? 'unlocked' : null,
auth()
->loggedIn() ? 'authenticated' : null,
]), ]),
); );
if (! ($cachedView = cache($cacheName))) { if (! ($cachedView = cache($cacheName))) {
set_podcast_metatags($this->podcast, 'activity');
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'notes' => (new NoteModel())->getActorPublishedNotes($this->podcast->actor_id), 'posts' => (new PostModel())->getActorPublishedPosts($this->podcast->actor_id),
]; ];
// if user is logged in then send to the authenticated activity view // if user is logged in then send to the authenticated activity view
if (can_user_interact()) { if (auth()->loggedIn()) {
helper('form'); helper('form');
return view('podcast/activity_authenticated', $data);
return view('podcast/activity', $data);
} }
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode( $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
...@@ -77,9 +93,7 @@ class PodcastController extends BaseController ...@@ -77,9 +93,7 @@ class PodcastController extends BaseController
); );
return view('podcast/activity', $data, [ return view('podcast/activity', $data, [
'cache' => $secondsToNextUnpublishedEpisode 'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
...@@ -87,13 +101,57 @@ class PodcastController extends BaseController ...@@ -87,13 +101,57 @@ class PodcastController extends BaseController
return $cachedView; return $cachedView;
} }
public function episodes(): string public function about(): string
{ {
// Prevent analytics hit when authenticated $this->registerPodcastWebpageHit($this->podcast->id);
if (! can_user_interact()) {
$this->registerPodcastWebpageHit($this->podcast->id); $cacheName = implode(
'_',
array_filter([
'page',
"podcast#{$this->podcast->id}",
'about',
service('request')
->getLocale(),
is_unlocked($this->podcast->handle) ? 'unlocked' : null,
auth()
->loggedIn() ? 'authenticated' : null,
]),
);
if (! ($cachedView = cache($cacheName))) {
$stats = (new EpisodeModel())->getPodcastStats($this->podcast->id);
set_podcast_metatags($this->podcast, 'about');
$data = [
'podcast' => $this->podcast,
'stats' => $stats,
];
// // if user is logged in then send to the authenticated activity view
if (auth()->loggedIn()) {
helper('form');
return view('podcast/about', $data);
}
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id,
);
return view('podcast/about', $data, [
'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
'cache_name' => $cacheName,
]);
} }
return $cachedView;
}
public function episodes(): string
{
$this->registerPodcastWebpageHit($this->podcast->id);
$yearQuery = $this->request->getGet('year'); $yearQuery = $this->request->getGet('year');
$seasonQuery = $this->request->getGet('season'); $seasonQuery = $this->request->getGet('season');
...@@ -118,7 +176,9 @@ class PodcastController extends BaseController ...@@ -118,7 +176,9 @@ class PodcastController extends BaseController
$seasonQuery ? 'season' . $seasonQuery : null, $seasonQuery ? 'season' . $seasonQuery : null,
service('request') service('request')
->getLocale(), ->getLocale(),
can_user_interact() ? '_authenticated' : null, is_unlocked($this->podcast->handle) ? 'unlocked' : null,
auth()
->loggedIn() ? 'authenticated' : null,
]), ]),
); );
...@@ -134,18 +194,17 @@ class PodcastController extends BaseController ...@@ -134,18 +194,17 @@ class PodcastController extends BaseController
$isActive = $yearQuery === $year['year']; $isActive = $yearQuery === $year['year'];
if ($isActive) { if ($isActive) {
$activeQuery = [ $activeQuery = [
'type' => 'year', 'type' => 'year',
'value' => $year['year'], 'value' => $year['year'],
'label' => $year['year'], 'label' => $year['year'],
'number_of_episodes' => $year['number_of_episodes'], 'number_of_episodes' => $year['number_of_episodes'],
]; ];
} }
$episodesNavigation[] = [ $episodesNavigation[] = [
'label' => $year['year'], 'label' => $year['year'],
'number_of_episodes' => $year['number_of_episodes'], 'number_of_episodes' => $year['number_of_episodes'],
'route' => 'route' => route_to('podcast-episodes', $this->podcast->handle) .
route_to('podcast-episodes', $this->podcast->name) .
'?year=' . '?year=' .
$year['year'], $year['year'],
'is_active' => $isActive, 'is_active' => $isActive,
...@@ -156,7 +215,7 @@ class PodcastController extends BaseController ...@@ -156,7 +215,7 @@ class PodcastController extends BaseController
$isActive = $seasonQuery === $season['season_number']; $isActive = $seasonQuery === $season['season_number'];
if ($isActive) { if ($isActive) {
$activeQuery = [ $activeQuery = [
'type' => 'season', 'type' => 'season',
'value' => $season['season_number'], 'value' => $season['season_number'],
'label' => lang('Podcast.season', [ 'label' => lang('Podcast.season', [
'seasonNumber' => $season['season_number'], 'seasonNumber' => $season['season_number'],
...@@ -170,19 +229,19 @@ class PodcastController extends BaseController ...@@ -170,19 +229,19 @@ class PodcastController extends BaseController
'seasonNumber' => $season['season_number'], 'seasonNumber' => $season['season_number'],
]), ]),
'number_of_episodes' => $season['number_of_episodes'], 'number_of_episodes' => $season['number_of_episodes'],
'route' => 'route' => route_to('podcast-episodes', $this->podcast->handle) .
route_to('podcast-episodes', $this->podcast->name) .
'?season=' . '?season=' .
$season['season_number'], $season['season_number'],
'is_active' => $isActive, 'is_active' => $isActive,
]; ];
} }
set_podcast_metatags($this->podcast, 'episodes');
$data = [ $data = [
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'episodesNav' => $episodesNavigation, 'episodesNav' => $episodesNavigation,
'activeQuery' => $activeQuery, 'activeQuery' => $activeQuery,
'episodes' => (new EpisodeModel())->getPodcastEpisodes( 'episodes' => (new EpisodeModel())->getPodcastEpisodes(
$this->podcast->id, $this->podcast->id,
$this->podcast->type, $this->podcast->type,
$yearQuery, $yearQuery,
...@@ -190,22 +249,66 @@ class PodcastController extends BaseController ...@@ -190,22 +249,66 @@ class PodcastController extends BaseController
), ),
]; ];
if (auth()->loggedIn()) {
return view('podcast/episodes', $data);
}
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode( $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$this->podcast->id, $this->podcast->id,
); );
// if user is logged in then send to the authenticated episodes view
if (can_user_interact()) {
return view('podcast/episodes_authenticated', $data);
}
return view('podcast/episodes', $data, [ return view('podcast/episodes', $data, [
'cache' => $secondsToNextUnpublishedEpisode 'cache' => $secondsToNextUnpublishedEpisode ?: DECADE,
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
return $cachedView; return $cachedView;
} }
public function episodeCollection(): ResponseInterface
{
if ($this->podcast->type === 'serial') {
// podcast is serial
$episodes = model('EpisodeModel')
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->orderBy('season_number DESC, number ASC');
} else {
$episodes = model('EpisodeModel')
->where('`published_at` <= UTC_TIMESTAMP()', null, false)
->orderBy('published_at', 'DESC');
}
$pageNumber = (int) $this->request->getGet('page');
if ($pageNumber < 1) {
$episodes->paginate(12);
$pager = $episodes->pager;
$collection = new OrderedCollectionObject(null, $pager);
} else {
$paginatedEpisodes = $episodes->paginate(12, 'default', $pageNumber);
$pager = $episodes->pager;
$orderedItems = [];
if ($paginatedEpisodes !== null) {
foreach ($paginatedEpisodes as $episode) {
$orderedItems[] = (new PodcastEpisode($episode))->toArray();
}
}
// @phpstan-ignore-next-line
$collection = new OrderedCollectionPage($pager, $orderedItems);
}
return $this->response
->setContentType('application/activity+json')
->setBody($collection->toJSON());
}
public function links(): string
{
set_podcast_metatags($this->podcast, 'links');
return view('podcast/links', [
'podcast' => $this->podcast,
]);
}
} }
...@@ -3,28 +3,28 @@ ...@@ -3,28 +3,28 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Controllers; namespace App\Controllers;
use ActivityPub\Controllers\NoteController as ActivityPubNoteController;
use ActivityPub\Entities\Note as ActivityPubNote;
use Analytics\AnalyticsTrait;
use App\Entities\Actor; use App\Entities\Actor;
use App\Entities\Note as CastopodNote;
use App\Entities\Podcast; use App\Entities\Podcast;
use App\Entities\Post as CastopodPost;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\NoteModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\PostModel;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\URI; use CodeIgniter\HTTP\URI;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use Modules\Analytics\AnalyticsTrait;
use Modules\Fediverse\Controllers\PostController as FediversePostController;
use Override;
class NoteController extends ActivityPubNoteController class PostController extends FediversePostController
{ {
use AnalyticsTrait; use AnalyticsTrait;
...@@ -33,31 +33,41 @@ class NoteController extends ActivityPubNoteController ...@@ -33,31 +33,41 @@ class NoteController extends ActivityPubNoteController
protected Actor $actor; protected Actor $actor;
/** /**
* @var string[] * @var CastopodPost
*/ */
protected $helpers = ['auth', 'activitypub', 'svg', 'components', 'misc']; protected $post;
/**
* @var list<string>
*/
protected $helpers = ['auth', 'fediverse', 'svg', 'components', 'misc', 'seo', 'premium_podcasts'];
#[Override]
public function _remap(string $method, string ...$params): mixed public function _remap(string $method, string ...$params): mixed
{ {
if (count($params) < 2) {
throw PageNotFoundException::forPageNotFound();
}
if ( if (
($this->podcast = (new PodcastModel())->getPodcastByName($params[0])) === null ! ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) instanceof Podcast
) { ) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
$this->podcast = $podcast;
$this->actor = $this->podcast->actor; $this->actor = $this->podcast->actor;
if (count($params) <= 1) {
unset($params[0]);
return $this->{$method}(...$params);
}
if ( if (
($note = (new NoteModel())->getNoteById($params[1])) === null ! ($post = (new PostModel())->getPostById($params[1])) instanceof CastopodPost
) { ) {
throw PageNotFoundException::forPageNotFound(); throw PageNotFoundException::forPageNotFound();
} }
$this->note = $note; $this->post = $post;
unset($params[0]); unset($params[0]);
unset($params[1]); unset($params[1]);
...@@ -67,36 +77,35 @@ class NoteController extends ActivityPubNoteController ...@@ -67,36 +77,35 @@ class NoteController extends ActivityPubNoteController
public function view(): string public function view(): string
{ {
// Prevent analytics hit when authenticated $this->registerPodcastWebpageHit($this->podcast->id);
if (! can_user_interact()) {
$this->registerPodcastWebpageHit($this->podcast->id);
}
$cacheName = implode( $cacheName = implode(
'_', '_',
array_filter([ array_filter([
'page', 'page',
"note#{$this->note->id}", "post#{$this->post->id}",
service('request') service('request')
->getLocale(), ->getLocale(),
can_user_interact() ? '_authenticated' : null, auth()
->loggedIn() ? 'authenticated' : null,
]), ]),
); );
if (! ($cachedView = cache($cacheName))) { if (! ($cachedView = cache($cacheName))) {
set_post_metatags($this->post);
$data = [ $data = [
'post' => $this->post,
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'actor' => $this->actor,
'note' => $this->note,
]; ];
// if user is logged in then send to the authenticated activity view // if user is logged in then send to the authenticated activity view
if (can_user_interact()) { if (auth()->loggedIn()) {
helper('form'); helper('form');
return view('podcast/note_authenticated', $data); return view('post/post', $data);
} }
return view('podcast/note', $data, [
'cache' => DECADE, return view('post/post', $data, [
'cache' => DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
...@@ -104,11 +113,12 @@ class NoteController extends ActivityPubNoteController ...@@ -104,11 +113,12 @@ class NoteController extends ActivityPubNoteController
return $cachedView; return $cachedView;
} }
public function attemptCreate(): RedirectResponse #[Override]
public function createAction(): RedirectResponse
{ {
$rules = [ $rules = [
'message' => 'required|max_length[500]', 'message' => 'required|max_length[500]',
'episode_url' => 'valid_url|permit_empty', 'episode_url' => 'valid_url_strict|permit_empty',
]; ];
if (! $this->validate($rules)) { if (! $this->validate($rules)) {
...@@ -118,42 +128,45 @@ class NoteController extends ActivityPubNoteController ...@@ -118,42 +128,45 @@ class NoteController extends ActivityPubNoteController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$message = $this->request->getPost('message'); $validData = $this->validator->getValidated();
$message = $validData['message'];
$newNote = new CastopodNote([ $newPost = new CastopodPost([
'actor_id' => interact_as_actor_id(), 'actor_id' => interact_as_actor_id(),
'published_at' => Time::now(), 'published_at' => Time::now(),
'created_by' => user_id(), 'created_by' => user_id(),
]); ]);
// get episode if episodeUrl has been set // get episode if episodeUrl has been set
$episodeUri = $this->request->getPost('episode_url'); $episodeUri = $validData['episode_url'];
if ( if (
$episodeUri && $episodeUri &&
($params = extract_params_from_episode_uri(new URI($episodeUri))) && ($params = extract_params_from_episode_uri(new URI($episodeUri))) &&
($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastName'], $params['episodeSlug'])) ($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug']))
) { ) {
$newNote->episode_id = $episode->id; $newPost->episode_id = $episode->id;
} }
$newNote->message = $message; $newPost->message = $message;
$noteModel = new NoteModel(); $postModel = new PostModel();
if ( if (
! $noteModel ! $postModel
->addNote($newNote, ! (bool) $newNote->episode_id, true) ->addPost($newPost, ! (bool) $newPost->episode_id, true)
) { ) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $noteModel->errors()); ->with('errors', $postModel->errors());
} }
// Note has been successfully created // Post has been successfully created
return redirect()->back(); return redirect()->back();
} }
public function attemptReply(): RedirectResponse #[Override]
public function replyAction(): RedirectResponse
{ {
$rules = [ $rules = [
'message' => 'required|max_length[500]', 'message' => 'required|max_length[500]',
...@@ -166,41 +179,49 @@ class NoteController extends ActivityPubNoteController ...@@ -166,41 +179,49 @@ class NoteController extends ActivityPubNoteController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$newNote = new ActivityPubNote([ $validData = $this->validator->getValidated();
'actor_id' => interact_as_actor_id(),
'in_reply_to_id' => $this->note->id, $newPost = new CastopodPost([
'message' => $this->request->getPost('message'), 'actor_id' => interact_as_actor_id(),
'published_at' => Time::now(), 'in_reply_to_id' => $this->post->id,
'created_by' => user_id(), 'message' => $validData['message'],
'published_at' => Time::now(),
'created_by' => user_id(),
]); ]);
$noteModel = new NoteModel(); if ($this->post->episode_id !== null) {
if (! $noteModel->addReply($newNote)) { $newPost->episode_id = $this->post->episode_id;
}
$postModel = new PostModel();
if (! $postModel->addReply($newPost)) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $noteModel->errors()); ->with('errors', $postModel->errors());
} }
// Reply note without preview card has been successfully created // Reply post without preview card has been successfully created
return redirect()->back(); return redirect()->back();
} }
public function attemptFavourite(): RedirectResponse #[Override]
public function favouriteAction(): RedirectResponse
{ {
model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->note); model('FavouriteModel')->toggleFavourite(interact_as_actor(), $this->post);
return redirect()->back(); return redirect()->back();
} }
public function attemptReblog(): RedirectResponse #[Override]
public function reblogAction(): RedirectResponse
{ {
(new NoteModel())->toggleReblog(interact_as_actor(), $this->note); (new PostModel())->toggleReblog(interact_as_actor(), $this->post);
return redirect()->back(); return redirect()->back();
} }
public function attemptAction(): RedirectResponse public function action(): RedirectResponse
{ {
$rules = [ $rules = [
'action' => 'required|in_list[favourite,reblog,reply]', 'action' => 'required|in_list[favourite,reblog,reply]',
...@@ -213,46 +234,35 @@ class NoteController extends ActivityPubNoteController ...@@ -213,46 +234,35 @@ class NoteController extends ActivityPubNoteController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$action = $this->request->getPost('action'); $validData = $this->validator->getValidated();
$action = $validData['action'];
return match ($action) { return match ($action) {
'favourite' => $this->attemptFavourite(), 'favourite' => $this->favouriteAction(),
'reblog' => $this->attemptReblog(), 'reblog' => $this->reblogAction(),
'reply' => $this->attemptReply(), 'reply' => $this->replyAction(),
default => redirect() default => redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', 'error'), ->with('errors', 'error'),
}; };
} }
public function remoteAction(string $action): string public function remoteActionView(string $action): string
{ {
// Prevent analytics hit when authenticated $this->registerPodcastWebpageHit($this->podcast->id);
if (! can_user_interact()) {
$this->registerPodcastWebpageHit($this->podcast->id); set_remote_actions_metatags($this->post, $action);
} $data = [
'podcast' => $this->podcast,
$cacheName = implode( 'actor' => $this->actor,
'_', 'post' => $this->post,
array_filter(['page', "note#{$this->note->id}", "remote_{$action}", service('request') ->getLocale()]), 'action' => $action,
); ];
if (! ($cachedView = cache($cacheName))) {
$data = [
'podcast' => $this->podcast,
'actor' => $this->actor,
'note' => $this->note,
'action' => $action,
];
helper('form');
return view('podcast/note_remote_action', $data, [ helper('form');
'cache' => DECADE,
'cache_name' => $cacheName,
]);
}
return (string) $cachedView; // NO VIEW CACHING: form has a CSRF token which should change on each request
return view('post/remote_action', $data);
} }
} }
<?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\Entities\Podcast;
use App\Models\PodcastModel;
use CodeIgniter\Controller;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\ResponseInterface;
class WebmanifestController extends Controller
{
/**
* @var array<string, array<string, string>>
*/
final public const array THEME_COLORS = [
'pine' => [
'theme' => '#009486',
'background' => '#F0F9F8',
],
'lake' => [
'theme' => '#00ACE0',
'background' => '#F0F7F9',
],
'jacaranda' => [
'theme' => '#562CDD',
'background' => '#F2F0F9',
],
'crimson' => [
'theme' => '#F24562',
'background' => '#F9F0F2',
],
'amber' => [
'theme' => '#FF6224',
'background' => '#F9F3F0',
],
'onyx' => [
'theme' => '#040406',
'background' => '#F3F3F7',
],
];
public function index(): ResponseInterface
{
helper('misc');
$webmanifest = [
'name' => esc(service('settings') ->get('App.siteName')),
'description' => esc(service('settings') ->get('App.siteDescription')),
'lang' => service('request')
->getLocale(),
'start_url' => base_url(),
'display' => 'standalone',
'orientation' => 'portrait',
'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
'background_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['background'],
'icons' => [
[
'src' => get_site_icon_url('192'),
'type' => 'image/png',
'sizes' => '192x192',
],
[
'src' => get_site_icon_url('512'),
'type' => 'image/png',
'sizes' => '512x512',
],
],
];
return $this->response->setJSON($webmanifest);
}
public function podcastManifest(string $podcastHandle): ResponseInterface
{
if (
! ($podcast = (new PodcastModel())->getPodcastByHandle($podcastHandle)) instanceof Podcast
) {
throw PageNotFoundException::forPageNotFound();
}
$webmanifest = [
'name' => esc($podcast->title),
'short_name' => $podcast->at_handle,
'description' => $podcast->description,
'lang' => $podcast->language_code,
'start_url' => $podcast->link,
'scope' => '/' . $podcast->at_handle,
'display' => 'standalone',
'orientation' => 'portrait',
'theme_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['theme'],
'background_color' => self::THEME_COLORS[service('settings')->get('App.theme')]['background'],
'icons' => [
[
'src' => $podcast->cover->webmanifest192_url,
'type' => $podcast->cover->webmanifest192_mimetype,
'sizes' => '192x192',
],
[
'src' => $podcast->cover->webmanifest512_url,
'type' => $podcast->cover->webmanifest512_mimetype,
'sizes' => '512x512',
],
],
];
return $this->response->setJSON($webmanifest);
}
}
<?php
declare(strict_types=1);
/**
* Class AddPodcastUsers Creates podcast_users table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddPodcastsUsers extends Migration
{
public function up(): void
{
$this->forge->addField([
'podcast_id' => [
'type' => 'INT',
'unsigned' => true,
],
'user_id' => [
'type' => 'INT',
'unsigned' => true,
],
'group_id' => [
'type' => 'INT',
'unsigned' => true,
],
]);
$this->forge->addPrimaryKey(['user_id', 'podcast_id']);
$this->forge->addForeignKey('user_id', 'users', 'id', '', 'CASCADE');
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('group_id', 'auth_groups', 'id', '', 'CASCADE');
$this->forge->createTable('podcasts_users');
}
public function down(): void
{
$this->forge->dropTable('podcasts_users');
}
}
<?php
declare(strict_types=1);
/**
* Class AddEpisodeIdToNotes Adds episode_id field to activitypub_notes table in database
*
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddEpisodeIdToNotes extends Migration
{
public function up(): void
{
$prefix = $this->db->getPrefix();
$createQuery = <<<CODE_SAMPLE
ALTER TABLE {$prefix}activitypub_notes
ADD COLUMN `episode_id` INT UNSIGNED NULL AFTER `replies_count`,
ADD FOREIGN KEY {$prefix}activitypub_notes_episode_id_foreign(episode_id) REFERENCES {$prefix}episodes(id) ON DELETE CASCADE;
CODE_SAMPLE;
$this->db->query($createQuery);
}
public function down(): void
{
$this->forge->dropForeignKey('activitypub_notes', 'activitypub_notes_episode_id_foreign');
$this->forge->dropColumn('activitypub_notes', 'episode_id');
}
}
...@@ -5,39 +5,40 @@ declare(strict_types=1); ...@@ -5,39 +5,40 @@ declare(strict_types=1);
/** /**
* Class AddCategories Creates categories table in database * Class AddCategories Creates categories table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddCategories extends Migration class AddCategories extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'id' => [ 'id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'parent_id' => [ 'parent_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'null' => true, 'null' => true,
], ],
'code' => [ 'code' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
'apple_category' => [ 'apple_category' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
'google_category' => [ 'google_category' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
]); ]);
...@@ -47,6 +48,7 @@ class AddCategories extends Migration ...@@ -47,6 +48,7 @@ class AddCategories extends Migration
$this->forge->createTable('categories'); $this->forge->createTable('categories');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('categories'); $this->forge->dropTable('categories');
......
...@@ -5,27 +5,28 @@ declare(strict_types=1); ...@@ -5,27 +5,28 @@ declare(strict_types=1);
/** /**
* Class AddLanguages Creates languages table in database * Class AddLanguages Creates languages table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddLanguages extends Migration class AddLanguages extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'code' => [ 'code' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'comment' => 'ISO 639-1 language code', 'comment' => 'ISO 639-1 language code',
'constraint' => 2, 'constraint' => 2,
], ],
'native_name' => [ 'native_name' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
], ],
]); ]);
...@@ -33,6 +34,7 @@ class AddLanguages extends Migration ...@@ -33,6 +34,7 @@ class AddLanguages extends Migration
$this->forge->createTable('languages'); $this->forge->createTable('languages');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('languages'); $this->forge->dropTable('languages');
......
...@@ -5,35 +5,40 @@ declare(strict_types=1); ...@@ -5,35 +5,40 @@ declare(strict_types=1);
/** /**
* Class AddPodcasts Creates podcasts table in database * Class AddPodcasts Creates podcasts table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddPodcasts extends Migration class AddPodcasts extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'id' => [ 'id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'auto_increment' => true, 'auto_increment' => true,
], ],
'guid' => [
'type' => 'CHAR',
'constraint' => 36,
],
'actor_id' => [ 'actor_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'name' => [ 'handle' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
'title' => [ 'title' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
], ],
'description_markdown' => [ 'description_markdown' => [
...@@ -42,53 +47,51 @@ class AddPodcasts extends Migration ...@@ -42,53 +47,51 @@ class AddPodcasts extends Migration
'description_html' => [ 'description_html' => [
'type' => 'TEXT', 'type' => 'TEXT',
], ],
'image_path' => [ 'cover_id' => [
'type' => 'VARCHAR', 'type' => 'INT',
'constraint' => 255, 'unsigned' => true,
], ],
// constraint is 13 because the longest safe mimetype for images is image/svg+xml, 'banner_id' => [
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types 'type' => 'INT',
'image_mimetype' => [ 'unsigned' => true,
'type' => 'VARCHAR', 'null' => true,
'constraint' => 13,
], ],
'language_code' => [ 'language_code' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 2, 'constraint' => 2,
], ],
'category_id' => [ 'category_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'default' => 0, 'default' => 0,
], ],
'parental_advisory' => [ 'parental_advisory' => [
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['clean', 'explicit'], 'constraint' => ['clean', 'explicit'],
'null' => true, 'null' => true,
'default' => null,
], ],
'owner_name' => [ 'owner_name' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
], ],
'owner_email' => [ 'owner_email' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],
'publisher' => [ 'publisher' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
'null' => true, 'null' => true,
], ],
'type' => [ 'type' => [
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['episodic', 'serial'], 'constraint' => ['episodic', 'serial'],
'default' => 'episodic', 'default' => 'episodic',
], ],
'copyright' => [ 'copyright' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
'null' => true, 'null' => true,
], ],
'episode_description_footer_markdown' => [ 'episode_description_footer_markdown' => [
'type' => 'TEXT', 'type' => 'TEXT',
...@@ -99,99 +102,105 @@ class AddPodcasts extends Migration ...@@ -99,99 +102,105 @@ class AddPodcasts extends Migration
'null' => true, 'null' => true,
], ],
'is_blocked' => [ 'is_blocked' => [
'type' => 'TINYINT', 'type' => 'TINYINT',
'constraint' => 1, 'constraint' => 1,
'default' => 0, 'default' => 0,
], ],
'is_completed' => [ 'is_completed' => [
'type' => 'TINYINT', 'type' => 'TINYINT',
'constraint' => 1, 'constraint' => 1,
'default' => 0, 'default' => 0,
], ],
'is_locked' => [ 'is_locked' => [
'type' => 'TINYINT', 'type' => 'TINYINT',
'constraint' => 1, 'constraint' => 1,
'default' => 1, 'default' => 1,
], ],
'imported_feed_url' => [ 'imported_feed_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'comment' => 'comment' => 'The RSS feed URL if this podcast was imported, NULL otherwise.',
'The RSS feed URL if this podcast was imported, NULL otherwise.', 'null' => true,
'null' => true,
], ],
'new_feed_url' => [ 'new_feed_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'comment' => 'comment' => 'The RSS new feed URL if this podcast is moving out, NULL otherwise.',
'The RSS new feed URL if this podcast is moving out, NULL otherwise.', 'null' => true,
'null' => true,
], ],
'payment_pointer' => [ 'payment_pointer' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
'comment' => 'Wallet address for Web Monetization payments', 'comment' => 'Wallet address for Web Monetization payments',
'null' => true, 'null' => true,
], ],
'location_name' => [ 'location_name' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
'null' => true, 'null' => true,
], ],
'location_geo' => [ 'location_geo' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
'null' => true, 'null' => true,
], ],
'location_osm' => [ 'location_osm' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 12, 'constraint' => 12,
'null' => true, 'null' => true,
], ],
'custom_rss' => [ 'custom_rss' => [
'type' => 'JSON', 'type' => 'JSON',
'null' => true, 'null' => true,
], ],
'partner_id' => [ 'partner_id' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
'null' => true, 'null' => true,
], ],
'partner_link_url' => [ 'partner_link_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'null' => true, 'null' => true,
], ],
'partner_image_url' => [ 'partner_image_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'null' => true, 'null' => true,
],
'is_premium_by_default' => [
'type' => 'TINYINT',
'constraint' => 1,
'default' => 0,
], ],
'created_by' => [ 'created_by' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'updated_by' => [ 'updated_by' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'created_at' => [ 'published_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',
'null' => true,
], ],
'updated_at' => [ 'created_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',
], ],
'deleted_at' => [ 'updated_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',
'null' => true,
], ],
]); ]);
$this->forge->addPrimaryKey('id'); $this->forge->addPrimaryKey('id');
// TODO: remove name in favor of username from actor // TODO: remove name in favor of username from actor
$this->forge->addUniqueKey('name'); $this->forge->addUniqueKey('handle');
$this->forge->addUniqueKey('guid');
$this->forge->addUniqueKey('actor_id'); $this->forge->addUniqueKey('actor_id');
$this->forge->addForeignKey('actor_id', 'activitypub_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'); $this->forge->addForeignKey('category_id', 'categories', 'id');
$this->forge->addForeignKey('language_code', 'languages', 'code'); $this->forge->addForeignKey('language_code', 'languages', 'code');
$this->forge->addForeignKey('created_by', 'users', 'id'); $this->forge->addForeignKey('created_by', 'users', 'id');
...@@ -199,6 +208,7 @@ class AddPodcasts extends Migration ...@@ -199,6 +208,7 @@ class AddPodcasts extends Migration
$this->forge->createTable('podcasts'); $this->forge->createTable('podcasts');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('podcasts'); $this->forge->dropTable('podcasts');
......
...@@ -5,64 +5,45 @@ declare(strict_types=1); ...@@ -5,64 +5,45 @@ declare(strict_types=1);
/** /**
* Class AddEpisodes Creates episodes table in database * Class AddEpisodes Creates episodes table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddEpisodes extends Migration class AddEpisodes extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'id' => [ 'id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'auto_increment' => true, 'auto_increment' => true,
], ],
'podcast_id' => [ 'podcast_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'guid' => [ 'guid' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],
'title' => [ 'title' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
], ],
'slug' => [ 'slug' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 191, 'constraint' => 128,
],
'audio_file_path' => [
'type' => 'VARCHAR',
'constraint' => 255,
],
'audio_file_duration' => [
// exact value for duration with max 99999,999 ~ 27.7 hours
'type' => 'DECIMAL(8,3)',
'unsigned' => true,
'comment' => 'Playtime in seconds',
],
'audio_file_mimetype' => [
'type' => 'VARCHAR',
'constraint' => 255,
],
'audio_file_size' => [
'type' => 'INT',
'unsigned' => true,
'comment' => 'File size in bytes',
], ],
'audio_file_header_size' => [ 'audio_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'comment' => 'Header size in bytes',
], ],
'description_markdown' => [ 'description_markdown' => [
'type' => 'TEXT', 'type' => 'TEXT',
...@@ -70,104 +51,96 @@ class AddEpisodes extends Migration ...@@ -70,104 +51,96 @@ class AddEpisodes extends Migration
'description_html' => [ 'description_html' => [
'type' => 'TEXT', 'type' => 'TEXT',
], ],
'image_path' => [ 'cover_id' => [
'type' => 'VARCHAR', 'type' => 'INT',
'constraint' => 255, 'unsigned' => true,
'null' => true, 'null' => true,
],
// constraint is 13 because the longest safe mimetype for images is image/svg+xml,
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types
'image_mimetype' => [
'type' => 'VARCHAR',
'constraint' => 13,
'null' => true,
], ],
'transcript_file_path' => [ 'transcript_id' => [
'type' => 'VARCHAR', 'type' => 'INT',
'constraint' => 255, 'unsigned' => true,
'null' => true, 'null' => true,
], ],
'transcript_file_remote_url' => [ 'transcript_remote_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'null' => true, 'null' => true,
], ],
'chapters_file_path' => [ 'chapters_id' => [
'type' => 'VARCHAR', 'type' => 'INT',
'constraint' => 255, 'unsigned' => true,
'null' => true, 'null' => true,
], ],
'chapters_file_remote_url' => [ 'chapters_remote_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'null' => true, 'null' => true,
], ],
'parental_advisory' => [ 'parental_advisory' => [
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['clean', 'explicit'], 'constraint' => ['clean', 'explicit'],
'null' => true, 'null' => true,
'default' => null,
], ],
'number' => [ 'number' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'null' => true, 'null' => true,
], ],
'season_number' => [ 'season_number' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'null' => true, 'null' => true,
], ],
'type' => [ 'type' => [
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['trailer', 'full', 'bonus'], 'constraint' => ['trailer', 'full', 'bonus'],
'default' => 'full', 'default' => 'full',
], ],
'is_blocked' => [ 'is_blocked' => [
'type' => 'TINYINT', 'type' => 'TINYINT',
'constraint' => 1, 'constraint' => 1,
'default' => 0, 'default' => 0,
], ],
'location_name' => [ 'location_name' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
'null' => true, 'null' => true,
], ],
'location_geo' => [ 'location_geo' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
'null' => true, 'null' => true,
], ],
'location_osm' => [ 'location_osm' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 12, 'constraint' => 12,
'null' => true, 'null' => true,
], ],
'custom_rss' => [ 'custom_rss' => [
'type' => 'JSON', 'type' => 'JSON',
'null' => true, 'null' => true,
], ],
'favourites_total' => [ 'posts_count' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'default' => 0, 'default' => 0,
], ],
'reblogs_total' => [ 'comments_count' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'default' => 0, 'default' => 0,
], ],
'notes_total' => [ 'is_premium' => [
'type' => 'INT', 'type' => 'TINYINT',
'unsigned' => true, 'constraint' => 1,
'default' => 0, 'default' => 0,
], ],
'created_by' => [ 'created_by' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'updated_by' => [ 'updated_by' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'published_at' => [ 'published_at' => [
...@@ -180,19 +153,28 @@ class AddEpisodes extends Migration ...@@ -180,19 +153,28 @@ class AddEpisodes extends Migration
'updated_at' => [ 'updated_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',
], ],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addPrimaryKey('id'); $this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey(['podcast_id', 'slug']); $this->forge->addUniqueKey(['podcast_id', 'slug']);
$this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE'); $this->forge->addForeignKey('podcast_id', 'podcasts', 'id', '', 'CASCADE');
$this->forge->addForeignKey('audio_id', 'media', 'id');
$this->forge->addForeignKey('cover_id', 'media', 'id', '', 'SET NULL');
$this->forge->addForeignKey('transcript_id', 'media', 'id', '', 'SET NULL');
$this->forge->addForeignKey('chapters_id', 'media', 'id', '', 'SET NULL');
$this->forge->addForeignKey('created_by', 'users', 'id'); $this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->addForeignKey('updated_by', 'users', 'id'); $this->forge->addForeignKey('updated_by', 'users', 'id');
$this->forge->createTable('episodes'); $this->forge->createTable('episodes');
// Add Full-Text Search index on title and description_markdown
$prefix = $this->db->getPrefix();
$createQuery = <<<SQL
ALTER TABLE {$prefix}episodes
ADD FULLTEXT title (title, description_markdown);
SQL;
$this->db->query($createQuery);
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('episodes'); $this->forge->dropTable('episodes');
......
...@@ -5,49 +5,52 @@ declare(strict_types=1); ...@@ -5,49 +5,52 @@ declare(strict_types=1);
/** /**
* Class AddPlatforms Creates platforms table in database * Class AddPlatforms Creates platforms table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddPlatforms extends Migration class AddPlatforms extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'slug' => [ 'slug' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
'type' => [ 'type' => [
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['podcasting', 'social', 'funding'], 'constraint' => ['podcasting', 'social', 'funding'],
], ],
'label' => [ 'label' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
'home_url' => [ 'home_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],
'submit_url' => [ 'submit_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'null' => true, 'null' => true,
'default' => null,
], ],
]); ]);
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT NOW()'); $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()');
$this->forge->addField('`updated_at` timestamp NOT NULL DEFAULT NOW() ON UPDATE NOW()'); $this->forge->addField(
'`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()',
);
$this->forge->addPrimaryKey('slug'); $this->forge->addPrimaryKey('slug');
$this->forge->createTable('platforms'); $this->forge->createTable('platforms');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('platforms'); $this->forge->dropTable('platforms');
......
...@@ -5,53 +5,57 @@ declare(strict_types=1); ...@@ -5,53 +5,57 @@ declare(strict_types=1);
/** /**
* Class AddAddPodcastsPlatforms Creates podcasts_platforms table in database * Class AddAddPodcastsPlatforms Creates podcasts_platforms table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace Analytics\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddPodcastsPlatforms extends Migration class AddPodcastsPlatforms extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'podcast_id' => [ 'podcast_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'platform_slug' => [ 'platform_slug' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 32, 'constraint' => 32,
], ],
'link_url' => [ 'link_url' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
], ],
'link_content' => [ 'account_id' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 128, 'constraint' => 128,
'null' => true, 'null' => true,
], ],
'is_visible' => [ 'is_visible' => [
'type' => 'TINYINT', 'type' => 'TINYINT',
'constraint' => 1, 'constraint' => 1,
'default' => 0, 'default' => 0,
], ],
'is_on_embeddable_player' => [ 'is_on_embed' => [
'type' => 'TINYINT', 'type' => 'TINYINT',
'constraint' => 1, 'constraint' => 1,
'default' => 0, 'default' => 0,
], ],
]); ]);
$this->forge->addPrimaryKey(['podcast_id', 'platform_slug']); $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'); $this->forge->createTable('podcasts_platforms');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('podcasts_platforms'); $this->forge->dropTable('podcasts_platforms');
......
<?php
declare(strict_types=1);
/**
* Class AddEpisodeComments creates episode_comments table in database
*
* @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Database\Migrations;
use Override;
class AddEpisodeComments extends BaseMigration
{
#[Override]
public function up(): void
{
$this->forge->addField([
'id' => [
'type' => 'BINARY',
'constraint' => 16,
],
'uri' => [
'type' => 'VARCHAR',
'constraint' => 255,
],
'episode_id' => [
'type' => 'INT',
'unsigned' => true,
],
'actor_id' => [
'type' => 'INT',
'unsigned' => true,
],
'in_reply_to_id' => [
'type' => 'BINARY',
'constraint' => 16,
'null' => true,
],
'message' => [
'type' => 'VARCHAR',
'constraint' => 5000,
],
'message_html' => [
'type' => 'VARCHAR',
'constraint' => 6000,
],
'likes_count' => [
'type' => 'INT',
'unsigned' => true,
],
'replies_count' => [
'type' => 'INT',
'unsigned' => true,
],
'created_at' => [
'type' => 'DATETIME',
],
'created_by' => [
'type' => 'INT',
'unsigned' => true,
'null' => true,
],
]);
$this->forge->addPrimaryKey('id');
$this->forge->addForeignKey('episode_id', 'episodes', 'id', '', 'CASCADE');
$this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('created_by', 'users', 'id');
$this->forge->createTable('episode_comments');
}
#[Override]
public function down(): void
{
$this->forge->dropTable('episode_comments');
}
}
...@@ -3,40 +3,43 @@ ...@@ -3,40 +3,43 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* Class AddFavourites Creates activitypub_favourites table in database * Class AddLikes Creates likes table in database
* *
* @copyright 2021 Podlibre * @copyright 2021 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace ActivityPub\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddFavourites extends Migration class AddLikes extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'actor_id' => [ 'actor_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'note_id' => [ 'comment_id' => [
'type' => 'BINARY', 'type' => 'BINARY',
'constraint' => 16, 'constraint' => 16,
], ],
]); ]);
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()'); $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT current_timestamp()');
$this->forge->addPrimaryKey(['actor_id', 'note_id']); $this->forge->addPrimaryKey(['actor_id', 'comment_id']);
$this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); $this->forge->addForeignKey('actor_id', 'fediverse_actors', 'id', '', 'CASCADE');
$this->forge->addForeignKey('note_id', 'activitypub_notes', 'id', '', 'CASCADE'); $this->forge->addForeignKey('comment_id', 'episode_comments', 'id', '', 'CASCADE');
$this->forge->createTable('activitypub_favourites'); $this->forge->createTable('likes');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('activitypub_favourites'); $this->forge->dropTable('likes');
} }
} }
...@@ -5,33 +5,34 @@ declare(strict_types=1); ...@@ -5,33 +5,34 @@ declare(strict_types=1);
/** /**
* Class AddPages Creates pages table in database * Class AddPages Creates pages table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddPages extends Migration class AddPages extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'id' => [ 'id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'auto_increment' => true, 'auto_increment' => true,
], ],
'title' => [ 'title' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 255, 'constraint' => 255,
], ],
'slug' => [ 'slug' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 191, 'constraint' => 128,
'unique' => true, 'unique' => true,
], ],
'content_markdown' => [ 'content_markdown' => [
'type' => 'TEXT', 'type' => 'TEXT',
...@@ -45,15 +46,12 @@ class AddPages extends Migration ...@@ -45,15 +46,12 @@ class AddPages extends Migration
'updated_at' => [ 'updated_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',
], ],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]); ]);
$this->forge->addPrimaryKey('id'); $this->forge->addPrimaryKey('id');
$this->forge->createTable('pages'); $this->forge->createTable('pages');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('pages'); $this->forge->dropTable('pages');
......
...@@ -5,26 +5,27 @@ declare(strict_types=1); ...@@ -5,26 +5,27 @@ declare(strict_types=1);
/** /**
* Class AddPodcastsCategories Creates podcasts_categories table in database * Class AddPodcastsCategories Creates podcasts_categories table in database
* *
* @copyright 2020 Podlibre * @copyright 2020 Ad Aures
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/ * @link https://castopod.org/
*/ */
namespace App\Database\Migrations; namespace App\Database\Migrations;
use CodeIgniter\Database\Migration; use Override;
class AddPodcastsCategories extends Migration class AddPodcastsCategories extends BaseMigration
{ {
#[Override]
public function up(): void public function up(): void
{ {
$this->forge->addField([ $this->forge->addField([
'podcast_id' => [ 'podcast_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
'category_id' => [ 'category_id' => [
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
], ],
]); ]);
...@@ -34,6 +35,7 @@ class AddPodcastsCategories extends Migration ...@@ -34,6 +35,7 @@ class AddPodcastsCategories extends Migration
$this->forge->createTable('podcasts_categories'); $this->forge->createTable('podcasts_categories');
} }
#[Override]
public function down(): void public function down(): void
{ {
$this->forge->dropTable('podcasts_categories'); $this->forge->dropTable('podcasts_categories');
......