Commit ac5f0c73 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat: edit + delete podcast and episode

- refactor model / entity and controller logic for DRY code
- update episodes and podcasts
migrations
- define callbacks for podcast and episode models for enclosure update and cache
clearing
parent c815ecd6
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -38,6 +38,12 @@ $routes->add('new-podcast', 'Podcast::create', ['as' => 'podcast_create']);

$routes->group('@(:podcastName)', function ($routes) {
    $routes->add('/', 'Podcast::view/$1', ['as' => 'podcast_view']);
    $routes->add('edit', 'Podcast::edit/$1', [
        'as' => 'podcast_edit',
    ]);
    $routes->add('delete', 'Podcast::delete/$1', [
        'as' => 'podcast_delete',
    ]);
    $routes->add('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
    $routes->add('new-episode', 'Episode::create/$1', [
        'as' => 'episode_create',
@@ -45,6 +51,12 @@ $routes->group('@(:podcastName)', function ($routes) {
    $routes->add('episodes/(:episodeSlug)', 'Episode::view/$1/$2', [
        'as' => 'episode_view',
    ]);
    $routes->add('episodes/(:episodeSlug)/edit', 'Episode::edit/$1/$2', [
        'as' => 'episode_edit',
    ]);
    $routes->add('episodes/(:episodeSlug)/delete', 'Episode::delete/$1/$2', [
        'as' => 'episode_delete',
    ]);
});

// Route for podcast audio file analytics (/stats/podcast_id/episode_id/podcast_folder/filename.mp3)
+126 −67
Original line number Diff line number Diff line
@@ -10,23 +10,44 @@ namespace App\Controllers;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;

helper('podcast');

class Episode extends BaseController
{
    public function create($podcast_name)
    protected \App\Entities\Podcast $podcast;
    protected ?\App\Entities\Episode $episode;

    public function _remap($method, ...$params)
    {
        helper(['form', 'database', 'media', 'id3']);
        $podcast_model = new PodcastModel();

        $this->podcast = $podcast_model->where('name', $params[0])->first();

        if (count($params) > 1) {
            $episode_model = new EpisodeModel();
        $podcast_model = new PodcastModel();
            if (
                !($episode = $episode_model
                    ->where([
                        'podcast_id' => $this->podcast->id,
                        'slug' => $params[1],
                    ])
                    ->first())
            ) {
                throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
            }
            $this->episode = $episode;
        }

        $podcast = $podcast_model->where('name', $podcast_name)->first();
        return $this->$method();
    }

    public function create()
    {
        helper(['form']);

        if (
            !$this->validate([
                'episode_file' =>
                    'uploaded[episode_file]|ext_in[episode_file,mp3,m4a]',
                'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
                'image' =>
                    'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
                'title' => 'required',
                'slug' => 'required',
                'description' => 'required',
@@ -34,58 +55,19 @@ class Episode extends BaseController
            ])
        ) {
            $data = [
                'podcast' => $podcast,
                'podcast' => $this->podcast,
            ];

            echo view('episode/create', $data);
        } else {
            $episode_slug = $this->request->getVar('slug');

            $episode_file = $this->request->getFile('episode_file');
            $episode_file_metadata = get_file_tags($episode_file);

            $image = $this->request->getFile('image');

            // By default, the episode's image path is set to the podcast's
            $image_path = $podcast->image_uri;

            // check whether the user has inputted an image and store it
            if ($image->isValid()) {
                $image_path = save_podcast_media(
                    $image,
                    $podcast_name,
                    $episode_slug
                );
            } elseif ($APICdata = $episode_file_metadata['attached_picture']) {
                // if the user didn't input an image,
                // check if the uploaded audio file has an attached cover and store it
                $cover_image = new \CodeIgniter\Files\File('episode_cover');
                file_put_contents($cover_image, $APICdata);

                $image_path = save_podcast_media(
                    $cover_image,
                    $podcast_name,
                    $episode_slug
                );
            }

            $episode_path = save_podcast_media(
                $episode_file,
                $podcast_name,
                $episode_slug
            );

            $episode = new \App\Entities\Episode([
                'podcast_id' => $podcast->id,
            $new_episode = new \App\Entities\Episode([
                'podcast_id' => $this->podcast->id,
                'title' => $this->request->getVar('title'),
                'slug' => $episode_slug,
                'enclosure_uri' => $episode_path,
                'enclosure_length' => $episode_file->getSize(),
                'enclosure_type' => $episode_file_metadata['mime_type'],
                'slug' => $this->request->getVar('slug'),
                'enclosure' => $this->request->getFile('enclosure'),
                'pub_date' => $this->request->getVar('pub_date'),
                'description' => $this->request->getVar('description'),
                'duration' => $episode_file_metadata['playtime_seconds'],
                'image_uri' => $image_path,
                'image' => $this->request->getFile('image'),
                'explicit' => $this->request->getVar('explicit') or false,
                'number' => $this->request->getVar('episode_number'),
                'season_number' => $this->request->getVar('season_number')
@@ -97,30 +79,107 @@ class Episode extends BaseController
                'block' => $this->request->getVar('block') or false,
            ]);

            $episode_model->save($episode);

            $episode_file = write_file_tags($podcast, $episode);
            $episode_model = new EpisodeModel();
            $episode_model->save($new_episode);

            return redirect()->to(
                base_url(route_to('episode_view', $podcast_name, $episode_slug))
                base_url(
                    route_to(
                        'episode_view',
                        $this->podcast->name,
                        $new_episode->slug
                    )
                )
            );
        }
    }

    public function view($podcast_name, $episode_slug)
    public function edit()
    {
        $podcast_model = new PodcastModel();
        helper(['form']);

        if (
            !$this->validate([
                'enclosure' =>
                    'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
                'image' =>
                    'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
                'title' => 'required',
                'slug' => 'required',
                'description' => 'required',
                'type' => 'required',
            ])
        ) {
            $data = [
                'podcast' => $this->podcast,
                'episode' => $this->episode,
            ];

            echo view('episode/edit', $data);
        } else {
            $this->episode->title = $this->request->getVar('title');
            $this->episode->slug = $this->request->getVar('slug');
            $this->episode->pub_date = $this->request->getVar('pub_date');
            $this->episode->description = $this->request->getVar('description');
            $this->episode->explicit =
                ($this->request->getVar('explicit') or false);
            $this->episode->number = $this->request->getVar('episode_number');
            $this->episode->season_number = $this->request->getVar(
                'season_number'
            )
                ? $this->request->getVar('season_number')
                : null;
            $this->episode->type = $this->request->getVar('type');
            $this->episode->author_name = $this->request->getVar('author_name');
            $this->episode->author_email = $this->request->getVar(
                'author_email'
            );
            $this->episode->block = ($this->request->getVar('block') or false);

            $enclosure = $this->request->getFile('enclosure');
            if ($enclosure->isValid()) {
                $this->episode->enclosure = $this->request->getFile(
                    'enclosure'
                );
            }
            $image = $this->request->getFile('image');
            if ($image) {
                $this->episode->image = $this->request->getFile('image');
            }

            $episode_model = new EpisodeModel();
            $episode_model->save($this->episode);

        $podcast = $podcast_model->where('name', $podcast_name)->first();
        $episode = $episode_model->where('slug', $episode_slug)->first();
            return redirect()->to(
                base_url(
                    route_to(
                        'episode_view',
                        $this->podcast->name,
                        $this->episode->slug
                    )
                )
            );
        }
    }

    public function view()
    {
        self::triggerWebpageHit($this->podcast->id);

        $data = [
            'podcast' => $podcast,
            'episode' => $episode,
            'podcast' => $this->podcast,
            'episode' => $this->episode,
        ];
        self::triggerWebpageHit($data['podcast']->id);
        return view('episode/view', $data);
    }

        return view('episode/view.php', $data);
    public function delete()
    {
        $episode_model = new EpisodeModel();
        $episode_model->delete($this->episode->id);

        return redirect()->to(
            base_url(route_to('podcast_view', $this->podcast->name))
        );
    }
}
+93 −20
Original line number Diff line number Diff line
@@ -7,15 +7,26 @@
namespace App\Controllers;

use App\Models\CategoryModel;
use App\Models\EpisodeModel;
use App\Models\LanguageModel;
use App\Models\PodcastModel;

class Podcast extends BaseController
{
    protected ?\App\Entities\Podcast $podcast;

    public function _remap($method, ...$params)
    {
        if (count($params) > 0) {
            $podcast_model = new PodcastModel();
            $this->podcast = $podcast_model->where('name', $params[0])->first();
        }

        return $this->$method();
    }

    public function create()
    {
        helper(['form', 'database', 'media', 'misc']);
        helper(['form', 'misc']);
        $podcast_model = new PodcastModel();

        if (
@@ -24,8 +35,8 @@ class Podcast extends BaseController
                'name' => 'required|regex_match[^[a-z0-9\_]{1,191}$]',
                'description' => 'required|max_length[4000]',
                'image' =>
                    'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|max_dims[image,3000,3000]',
                'owner_email' => 'required|valid_email|permit_empty',
                    'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
                'owner_email' => 'required|valid_email',
                'type' => 'required',
            ])
        ) {
@@ -41,18 +52,14 @@ class Podcast extends BaseController

            echo view('podcast/create', $data);
        } else {
            $image = $this->request->getFile('image');
            $podcast_name = $this->request->getVar('name');
            $image_path = save_podcast_media($image, $podcast_name, 'cover');

            $podcast = new \App\Entities\Podcast([
                'title' => $this->request->getVar('title'),
                'name' => $podcast_name,
                'name' => $this->request->getVar('name'),
                'description' => $this->request->getVar('description'),
                'episode_description_footer' => $this->request->getVar(
                    'episode_description_footer'
                ),
                'image_uri' => $image_path,
                'image' => $this->request->getFile('image'),
                'language' => $this->request->getVar('language'),
                'category' => $this->request->getVar('category'),
                'explicit' => $this->request->getVar('explicit') or false,
@@ -77,20 +84,86 @@ class Podcast extends BaseController
        }
    }

    public function view($podcast_name)
    public function edit()
    {
        $podcast_model = new PodcastModel();
        $episode_model = new EpisodeModel();
        helper(['form', 'misc']);

        $podcast = $podcast_model->where('name', $podcast_name)->first();
        if (
            !$this->validate([
                'title' => 'required',
                'name' => 'required|regex_match[^[a-z0-9\_]{1,191}$]',
                'description' => 'required|max_length[4000]',
                'image' =>
                    'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
                'owner_email' => 'required|valid_email',
                'type' => 'required',
            ])
        ) {
            $languageModel = new LanguageModel();
            $categoryModel = new CategoryModel();
            $data = [
            'podcast' => $podcast,
            'episodes' => $episode_model
                ->where('podcast_id', $podcast->id)
                ->findAll(),
                'podcast' => $this->podcast,
                'languages' => $languageModel->findAll(),
                'categories' => $categoryModel->findAll(),
            ];
        self::triggerWebpageHit($podcast->id);

            echo view('podcast/edit', $data);
        } else {
            $this->podcast->title = $this->request->getVar('title');
            $this->podcast->name = $this->request->getVar('name');
            $this->podcast->description = $this->request->getVar('description');
            $this->podcast->episode_description_footer = $this->request->getVar(
                'episode_description_footer'
            );

            $image = $this->request->getFile('image');
            if ($image->isValid()) {
                $this->podcast->image = $this->request->getFile('image');
            }
            $this->podcast->language = $this->request->getVar('language');
            $this->podcast->category = $this->request->getVar('category');
            $this->podcast->explicit =
                ($this->request->getVar('explicit') or false);
            $this->podcast->author_name = $this->request->getVar('author_name');
            $this->podcast->author_email = $this->request->getVar(
                'author_email'
            );
            $this->podcast->owner_name = $this->request->getVar('owner_name');
            $this->podcast->owner_email = $this->request->getVar('owner_email');
            $this->podcast->type = $this->request->getVar('type');
            $this->podcast->copyright = $this->request->getVar('copyright');
            $this->podcast->block = ($this->request->getVar('block') or false);
            $this->podcast->complete =
                ($this->request->getVar('complete') or false);
            $this->podcast->custom_html_head = $this->request->getVar(
                'custom_html_head'
            );

            $podcast_model = new PodcastModel();
            $podcast_model->save($this->podcast);

            return redirect()->to(
                base_url(route_to('podcast_view', $this->podcast->name))
            );
        }
    }

    public function view()
    {
        self::triggerWebpageHit($this->podcast->id);

        $data = [
            'podcast' => $this->podcast,
            'episodes' => $this->podcast->episodes,
        ];
        return view('podcast/view', $data);
    }

    public function delete()
    {
        $podcast_model = new PodcastModel();
        $podcast_model->delete($this->podcast->id);

        return redirect()->to(base_url(route_to('home')));
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -141,6 +141,10 @@ class AddPodcasts extends Migration
            'updated_at' => [
                'type' => 'TIMESTAMP',
            ],
            'deleted_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('podcasts');
+5 −19
Original line number Diff line number Diff line
@@ -47,25 +47,6 @@ class AddEpisodes extends Migration
                'comment' =>
                    'The URI attribute points to your podcast media file. The file extension specified within the URI attribute determines whether or not content appears in the podcast directory. Supported file formats include M4A, MP3, MOV, MP4, M4V, and PDF.',
            ],
            'enclosure_length' => [
                'type' => 'INT',
                'constraint' => 10,
                'unsigned' => true,
                'comment' =>
                    'The length attribute is the file size in bytes. You can find this information in the properties of your podcast file (on a Mac, choose File > Get Info and refer to the size field).',
            ],
            'enclosure_type' => [
                'type' => 'VARCHAR',
                'constraint' => 1024,
                'comment' =>
                    'The type attribute provides the correct category for the type of file you are using. The type values for the supported file formats are: audio/x-m4a, audio/mpeg, video/quicktime, video/mp4, video/x-m4v, and application/pdf.',
            ],
            'guid' => [
                'type' => 'VARCHAR',
                'constraint' => 1024,
                'comment' =>
                    'The episode’s globally unique identifier (GUID). It is very important that each episode have a unique GUID and that it never changes, even if an episode’s metadata, like title or enclosure URL, do change. Globally unique identifiers (GUID) are case-sensitive strings. If a GUID is not provided an episode’s enclosure URL will be used instead. If a GUID is not provided, make sure that an episode’s enclosure URL is unique and never changes. Failing to comply with these guidelines may result in duplicate episodes being shown to listeners, inaccurate data in Podcast Analytics, and can cause issues with your podcasts’s listing and chart placement in Apple Podcasts.',
            ],
            'pub_date' => [
                'type' => 'DATETIME',
                'comment' =>
@@ -87,6 +68,7 @@ class AddEpisodes extends Migration
            'image_uri' => [
                'type' => 'VARCHAR',
                'constraint' => 1024,
                'null' => true,
                'comment' =>
                    'The artwork for the show. Specify your show artwork by providing a URL linking to it. Depending on their device, subscribers see your podcast artwork in varying sizes. Therefore, make sure your design is effective at both its original size and at thumbnail size. You should include a show title, brand, or source name as part of your podcast artwork. Artwork must be a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate file extensions (.jpg, .png), and in the RGB colorspace.',
            ],
@@ -146,6 +128,10 @@ class AddEpisodes extends Migration
            'updated_at' => [
                'type' => 'TIMESTAMP',
            ],
            'deleted_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->addUniqueKey(['podcast_id', 'slug']);
Loading