Skip to content

Commit

Permalink
Sync videos list of channel and list videos paginated
Browse files Browse the repository at this point in the history
- Remove identifier to remote_identifier in Channel
- Add Video Sync and list paginated with newest one first
- Add jobs to make jobs run in background
- Sync videos from youtube to database and insert only the new ones
  • Loading branch information
AhmedHelalAhmed committed Sep 4, 2021
1 parent f9dc94f commit 38acda0
Show file tree
Hide file tree
Showing 18 changed files with 907 additions and 34 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
YOUTUBE_BASE_URL_API="https://www.googleapis.com/youtube/v3/"
YOUTUBE_API_KEY=
YOUTUBE_CHANNEL_URL_API="${YOUTUBE_BASE_URL_API}channels"
YOUTUBE_VIDEO_BASE_LINK="https://www.youtube.com/watch?v="
8 changes: 7 additions & 1 deletion app/Http/Controllers/ShowingChannelController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@


use App\Models\Channel;
use App\Models\Video;

class ShowingChannelController
{
public function __invoke(Channel $channel)
{
return inertia()->render('Channels/Videos/index', ['channel' => $channel]);
return inertia()->render('Channels/Videos/index', [
'channel' => $channel,
'videos' => Video::where('channel_id', $channel->id)
->orderByDesc('published_at')
->paginate()
]);
}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/StoringChannelController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class StoringChannelController
{
public function __invoke()
{
Channel::create(array_merge(request()->only(['name', 'identifier']),['user_id'=>auth()->id()]));
Channel::create(array_merge(request()->only(['name', 'remote_identifier']),['user_id'=>auth()->id()]));

return back();
}
Expand Down
15 changes: 15 additions & 0 deletions app/Http/Controllers/SyncVideosController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Http\Controllers;

use App\Jobs\SyncVideosForGivenChannelJob;
use App\Models\Channel;

class SyncVideosController
{
public function __invoke(Channel $channel)
{
dispatch(new SyncVideosForGivenChannelJob($channel));
return back();
}
}
42 changes: 42 additions & 0 deletions app/Jobs/SyncVideosForGivenChannelJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Jobs;

use App\Models\Channel;
use App\Services\SyncVideosForGivenChannelService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SyncVideosForGivenChannelJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* @var Channel
*/
private $channel;

/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Channel $channel)
{
$this->channel = $channel;
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
(new SyncVideosForGivenChannelService())->execute($this->channel);
}
}
7 changes: 6 additions & 1 deletion app/Models/Channel.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ class Channel extends Model

protected $fillable = [
'name',
'identifier',
'remote_identifier',
'user_id'
];

public function videos()
{
return $this->hasMany(Video::class);
}
}
56 changes: 56 additions & 0 deletions app/Models/Video.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Video extends Model
{
use SoftDeletes;
use HasFactory;

protected $dates = [
'favorite_at',
'seen_at',
'published_at'
];
protected $fillable = [
'title',
'description',
'thumbnail',
'channel_id',
'published_at',
'seen_at',
'favorite_at',
'remote_identifier',
];

protected $appends=[
'show_more',
'link'
];

public function channel()
{
return $this->belongsTo(Channel::class);
}

public function getShowMoreAttribute()
{
return false;
}

public function getLinkAttribute()
{
return config('youtube.urls.video_base_link').$this->remote_identifier;
}

public function getPublishedAtAttribute($val)
{
$dateTime=Carbon::parse($val);
return $dateTime->diffForHumans().' | '.$dateTime->format('d-m-Y h:i:s a');
}
}
94 changes: 94 additions & 0 deletions app/Services/SyncVideosForGivenChannelService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php


namespace App\Services;


use App\Models\Channel;
use App\Models\Video;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

class SyncVideosForGivenChannelService
{

private $videosData = [];

public function execute(Channel $channel)
{
$response = Http::get(config('youtube.urls.channel'), [
'id' => $channel->remote_identifier,
'part' => 'contentDetails',
'key' => config('youtube.key')
])->json();
$playlist = Arr::get($response, 'items.0.contentDetails.relatedPlaylists.uploads');
$response = Http::get(config('youtube.urls.videos'), [
'part' => 'snippet',
'playlistId' => $playlist,
'maxResults' => '50',
'key' => config('youtube.key')
])->json();
$this->AddMoreVideosData(Arr::get($response, 'items', []), $channel);
while (isset($response['nextPageToken'])) {
$response = Http::get(config('youtube.urls.videos'), [
'part' => 'snippet',
'playlistId' => $playlist,
'maxResults' => '50',
'key' => config('youtube.key'),
'pageToken' => Arr::get($response, 'nextPageToken')
])->json();

$this->AddMoreVideosData(Arr::get($response, 'items', []), $channel);
}

$videos = $this->filterVideosToNotExistsVideos($this->getExistsRemoteIds());

if ($videos->isNotEmpty()) {
return Video::insert($videos->toArray());
}
return true;
}

/**
* @param array $videosData
* @param $channel
*/
private function AddMoreVideosData(array $videosData, $channel)
{
foreach ($videosData as $video) {
$this->videosData[] = [
'remote_identifier' => Arr::get($video, 'snippet.resourceId.videoId'),
'title' => Arr::get($video, 'snippet.title'),
'description' => Arr::get($video, 'snippet.description'),
'thumbnail' => Arr::get($video, 'snippet.thumbnails.high.url'),
'channel_id' => $channel->id,
'published_at' => Carbon::parse(Arr::get($video, 'snippet.publishedAt')),
'created_at' => now(),
'updated_at' => now(),
];

}
}

/**
* @param $existsRemoteIds
* @return Collection
*/
public function filterVideosToNotExistsVideos($existsRemoteIds): Collection
{
return collect($this->videosData)->filter(function ($video) use ($existsRemoteIds) {
return !$existsRemoteIds->contains($video['remote_identifier']);
});
}

/**
* @return mixed
*/
public function getExistsRemoteIds()
{
return Video::Select('remote_identifier')->pluck('remote_identifier');
}

}
14 changes: 14 additions & 0 deletions config/youtube.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

return [

'urls' => [
'base'=> env('YOUTUBE_BASE_URL_API'),
'channel' => env('YOUTUBE_CHANNEL_URL_API'),
'videos' => env('YOUTUBE_CHANNEL_videos_URL_API'),
'video_base_link' => env('YOUTUBE_VIDEO_BASE_LINK'),
],


'key' => env('YOUTUBE_API_KEY'),
];
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public function up()
Schema::create('channels', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('identifier');
$table->foreignId('user_id');
$table->string('remote_identifier');
$table->foreignId('user_id')->constrained();
$table->timestamps();
});
}
Expand Down
40 changes: 40 additions & 0 deletions database/migrations/2021_09_03_214916_create_videos_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateVideosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('videos', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->string('remote_identifier');
$table->string('thumbnail');
$table->foreignId('channel_id')->constrained();
$table->timestamp('favorite_at')->nullable();
$table->timestamp('published_at')->nullable();
$table->timestamp('seen_at')->nullable();
$table->softDeletes();
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('videos');
}
}
36 changes: 36 additions & 0 deletions database/migrations/2021_09_03_224355_create_jobs_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('jobs');
}
}
Loading

0 comments on commit 38acda0

Please sign in to comment.