feat: add permanent delete feature for podcasts 🎉

closes #89
This commit is contained in:
Ola Hneini 2022-06-07 11:13:06 +00:00 committed by Yassine Doghri
parent 9548337a7c
commit dbb4030da4
13 changed files with 228 additions and 32 deletions

View File

@ -183,10 +183,6 @@ class AddPodcasts extends Migration
'updated_at' => [
'type' => 'DATETIME',
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]);
$this->forge->addPrimaryKey('id');

View File

@ -127,12 +127,6 @@ class AuthSeeder extends Seeder
],
[
'name' => 'delete',
'description' =>
'Delete a podcast without removing it from database',
'has_permission' => ['superadmin'],
],
[
'name' => 'delete_permanently',
'description' => 'Delete any podcast from the database',
'has_permission' => ['superadmin'],
],

View File

@ -81,7 +81,6 @@ use RuntimeException;
* @property int $updated_by
* @property Time $created_at;
* @property Time $updated_at;
* @property Time|null $deleted_at;
*
* @property Episode[] $episodes
* @property Person[] $persons

View File

@ -73,11 +73,6 @@ class PodcastModel extends Model
*/
protected $returnType = Podcast::class;
/**
* @var bool
*/
protected $useSoftDeletes = true;
/**
* @var bool
*/

View File

@ -127,6 +127,9 @@ $routes->group(
'as' => 'podcast-delete',
'filter' => 'permission:podcasts-delete',
]);
$routes->post('delete', 'PodcastController::attemptDelete/$1', [
'filter' => 'permission:podcasts-delete',
]);
$routes->group('persons', function ($routes): void {
$routes->get('/', 'PodcastPersonController/$1', [

View File

@ -739,8 +739,6 @@ class EpisodeController extends BaseController
->with('error', lang('Episode.messages.deletePublishedEpisodeError'));
}
$audio = $this->episode->audio;
$db = db_connect();
$db->transStart();
@ -755,7 +753,7 @@ class EpisodeController extends BaseController
->with('errors', $episodeModel->errors());
}
$episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $audio];
$episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $this->episode->audio];
//only delete episode cover if different from podcast's
if ($this->episode->cover_id !== null) {
@ -775,6 +773,8 @@ class EpisodeController extends BaseController
}
}
$db->transComplete();
$warnings = [];
//remove episode media files from disk
@ -787,8 +787,6 @@ class EpisodeController extends BaseController
}
}
$db->transComplete();
if ($warnings !== []) {
return redirect()
->route('episode-list', [$this->podcast->id])

View File

@ -21,6 +21,15 @@ use App\Models\PodcastModel;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Services;
use Modules\Analytics\Models\AnalyticsPodcastByCountryModel;
use Modules\Analytics\Models\AnalyticsPodcastByEpisodeModel;
use Modules\Analytics\Models\AnalyticsPodcastByHourModel;
use Modules\Analytics\Models\AnalyticsPodcastByPlayerModel;
use Modules\Analytics\Models\AnalyticsPodcastByRegionModel;
use Modules\Analytics\Models\AnalyticsPodcastModel;
use Modules\Analytics\Models\AnalyticsWebsiteByBrowserModel;
use Modules\Analytics\Models\AnalyticsWebsiteByEntryPageModel;
use Modules\Analytics\Models\AnalyticsWebsiteByRefererModel;
class PodcastController extends BaseController
{
@ -420,10 +429,166 @@ class PodcastController extends BaseController
]);
}
public function delete(): RedirectResponse
public function delete(): string
{
(new PodcastModel())->delete($this->podcast->id);
helper(['form']);
return redirect()->route('podcast-list');
$data = [
'podcast' => $this->podcast,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
]);
return view('podcast/delete', $data);
}
public function attemptDelete(): RedirectResponse
{
$rules = [
'understand' => 'required',
];
if (! $this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$db = db_connect();
$db->transStart();
//delete podcast episodes
$podcastEpisodes = (new EpisodeModel())->where('podcast_id', $this->podcast->id)
->findAll();
foreach ($podcastEpisodes as $podcastEpisode) {
$episodeModel = new EpisodeModel();
if (! $episodeModel->delete($podcastEpisode->id)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$episodeMediaList = [$podcastEpisode->transcript, $podcastEpisode->chapters, $podcastEpisode->audio];
//only delete episode cover if different from podcast's
if ($podcastEpisode->cover_id !== null) {
$episodeMediaList[] = $podcastEpisode->cover;
}
foreach ($episodeMediaList as $episodeMedia) {
if ($episodeMedia !== null && ! $episodeMedia->delete()) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('error', lang('Podcast.messages.deleteEpisodeMediaError', [
'episode_slug' => $podcastEpisode->slug,
'type' => $episodeMedia->type,
]));
}
}
}
//delete podcast
$podcastModel = new PodcastModel();
if (! $podcastModel->delete($this->podcast->id)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $podcastModel->errors());
}
//delete podcast media
$podcastMediaList = [
[
'type' => 'cover',
'file' => $this->podcast->cover,
],
[
'type' => 'banner',
'file' => $this->podcast->banner,
],
];
foreach ($podcastMediaList as $podcastMedia) {
if ($podcastMedia['file'] !== null && ! $podcastMedia['file']->delete()) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('error', lang('Podcast.messages.deletePodcastMediaError', [
'type' => $podcastMedia['type'],
]));
}
}
//delete podcast actor
$actorModel = new ActorModel();
if (! $actorModel->delete($this->podcast->actor_id)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $actorModel->errors());
}
//delete podcast analytics
$analyticsModels = [
new AnalyticsPodcastModel(),
new AnalyticsPodcastByCountryModel(),
new AnalyticsPodcastByEpisodeModel(),
new AnalyticsPodcastByHourModel(),
new AnalyticsPodcastByPlayerModel(),
new AnalyticsPodcastByRegionModel(),
new AnalyticsWebsiteByBrowserModel(),
new AnalyticsWebsiteByEntryPageModel(),
new AnalyticsWebsiteByRefererModel(),
];
foreach ($analyticsModels as $analyticsModel) {
if (! $analyticsModel->where([
'podcast_id' => $this->podcast->id,
])->delete()) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $analyticsModel->errors());
}
}
$db->transComplete();
//delete podcast media files and folder
$folder = 'podcasts/' . $this->podcast->handle;
$mediaRoot = config('App')
->mediaRoot . '/' . $folder;
helper('filesystem');
if (! delete_files($mediaRoot) || ! rmdir($mediaRoot)) {
return redirect()->route('podcast-list')
->with('message', lang('Podcast.messages.deleteSuccess', [
'podcast_handle' => $this->podcast->handle,
]))
->with('warning', lang('Podcast.messages.deletePodcastMediaFolderError', [
'folder_path' => $folder,
]));
}
return redirect()->route('podcast-list')
->with('message', lang('Podcast.messages.deleteSuccess', [
'podcast_handle' => $this->podcast->handle,
]));
}
}

View File

@ -63,7 +63,7 @@ return [
image {cover}
audio {audio}
other {media}
} file {file_path}. You must manually remove it from your disk.',
} file {file_path}. You may manually remove it from your disk.',
'sameSlugError' => 'An episode with the chosen slug already exists.',
],
'form' => [

View File

@ -26,6 +26,20 @@ return [
'createSuccess' => 'Podcast has been successfully created!',
'editSuccess' => 'Podcast has been successfully updated!',
'importSuccess' => 'Podcast has been successfully imported!',
'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!',
'deletePodcastMediaError' => 'Failed to delete podcast {type, select,
cover {cover}
banner {banner}
other {media}
}.',
'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select,
transcript {transcript}
chapters {chapters}
image {cover}
audio {audio}
other {media}
}.',
'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.',
],
'form' => [
'identity_section_title' => 'Podcast identity',
@ -219,6 +233,12 @@ return [
'film_reviews' => 'Film Reviews',
'tv_reviews' => 'TV Reviews',
],
'delete_form' => [
'disclaimer' =>
"Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.",
'understand' => 'I understand, I want the podcast to be permanently deleted',
'submit' => 'Delete',
],
'by' => 'By {publisher}',
'season' => 'Season {seasonNumber}',
'list_of_episodes_year' => '{year} episodes ({episodeCount})',

View File

@ -115,12 +115,6 @@ class AuthSeeder extends Seeder
],
[
'name' => 'delete',
'description' =>
'Delete a podcast without removing it from database',
'has_permission' => ['superadmin'],
],
[
'name' => 'delete_permanently',
'description' => 'Delete any podcast from the database',
'has_permission' => ['superadmin'],
],

View File

@ -18,6 +18,10 @@ if (session()->has('message')): ?>
</Alert>
<?php endif; ?>
<?php if (session()->has('warning')): ?>
<Alert variant="warning" class="mb-4"><?= esc(session('warning')) ?></Alert>
<?php endif; ?>
<?php if (session()->has('warnings')): ?>
<Alert variant="warning" class="mb-4">
<ul>

View File

@ -0,0 +1,27 @@
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Podcast.delete') ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Podcast.delete') ?>
<?= $this->endSection() ?>
<?= $this->section('content') ?>
<form action="<?= route_to('podcast-delete', $podcast->id) ?>" method="POST" class="flex flex-col w-full max-w-xl mx-auto">
<?= csrf_field() ?>
<Alert variant="danger" glyph="alert" class="font-semibold"><?= lang('Podcast.delete_form.disclaimer') ?></Alert>
<Forms.Checkbox class="mt-2" name="understand" required="true" isChecked="false"><?= lang('Podcast.delete_form.understand') ?></Forms.Checkbox>
<div class="self-end mt-4">
<Button uri="<?= route_to('podcast-view', $podcast->id) ?>"><?= lang('Common.cancel') ?></Button>
<Button type="submit" variant="danger"><?= lang('Podcast.delete_form.submit') ?></Button>
</div>
</form>
<?= $this->endSection() ?>

View File

@ -244,9 +244,10 @@
</Forms.Toggler>
</Forms.Section>
<Button variant="primary" type="submit" class="self-end"><?= lang('Podcast.form.submit_edit') ?></Button>
</div>
</form>
<Button class="mt-8" variant="danger" uri="<?= route_to('podcast-delete', $podcast->id) ?>" iconLeft="delete-bin"><?= lang('Podcast.delete') ?></Button>
<?= $this->endSection() ?>