Working interface and parsed symfony cast data to a database.
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\SymfonyCastDl\HtmlParser;
|
||||
use App\Http\SymfonyCastDl\SymfonyCastDlService;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
@@ -13,8 +11,4 @@ class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
public function index(HtmlParser $htmlParser)
|
||||
{
|
||||
$service = new SymfonyCastDlService($htmlParser);
|
||||
}
|
||||
}
|
||||
|
||||
25
app/Http/Controllers/CourseController.php
Normal file
25
app/Http/Controllers/CourseController.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\SymfonyCastDl\HtmlParser;
|
||||
use App\Http\SymfonyCastDl\SymfonyCastDlService;
|
||||
use App\Models\Chapter;
|
||||
use App\Models\Course;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class CourseController extends Controller
|
||||
{
|
||||
public function index(Course $course): View
|
||||
{
|
||||
return view('course.index', compact('course'));
|
||||
}
|
||||
|
||||
public function chapter(Chapter $chapter, HtmlParser $htmlParser)
|
||||
{
|
||||
$service = new SymfonyCastDlService($htmlParser);
|
||||
$service->videoSize($chapter);
|
||||
|
||||
dd($chapter->toArray());
|
||||
}
|
||||
}
|
||||
23
app/Http/Controllers/Index.php
Normal file
23
app/Http/Controllers/Index.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\SymfonyCastDl\HtmlParser;
|
||||
use App\Http\SymfonyCastDl\SymfonyCastDlService;
|
||||
use App\Models\Course;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Index extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$courses = Course::with('chapters')->withCount('chapters')->get();
|
||||
return view('index', compact(['courses']));
|
||||
}
|
||||
|
||||
public function download(HtmlParser $htmlParser)
|
||||
{
|
||||
$service = new SymfonyCastDlService($htmlParser);
|
||||
$service->getInfo();
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
namespace App\Http\SymfonyCastDl;
|
||||
|
||||
use App\Models\Chapter;
|
||||
use App\Models\Course;
|
||||
use Carbon\Carbon;
|
||||
use DiDom\Document;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class HtmlParser
|
||||
{
|
||||
@@ -13,36 +17,55 @@ class HtmlParser
|
||||
return $document->first('input[name="_csrf_token"]')->attr('value');
|
||||
}
|
||||
|
||||
public function getCourses(Response $response): array
|
||||
public function getCourses(Response $response): Collection
|
||||
{
|
||||
$courses = [];
|
||||
$courses = new Collection();
|
||||
$document = new Document($response->getBody()->getContents());
|
||||
foreach ($document->find('div.js-course-item') as $courseItem) {
|
||||
$course = [];
|
||||
$course['name'] = $courseItem->first('h3')->text();
|
||||
$course['link'] = $courseItem->first('a')->attr('href');
|
||||
$course['status'] = $courseItem->attr('data-status');
|
||||
$course['chapter-count'] = $courseItem->attr('data-chapter-count');
|
||||
$course['times-watched'] = $courseItem->attr('data-times-watched');
|
||||
|
||||
$courses[] = $course;
|
||||
$courseId = $courseItem->attr('data-id');
|
||||
$course = Course::firstOrNew(['course_id' => $courseId]);
|
||||
$course->name = $courseItem->first('h3')->text();
|
||||
$course->thumbnail = $courseItem->first('img.course-list-item-img')->attr('src');
|
||||
$course->link = $courseItem->first('a')->attr('href');
|
||||
$course->status = $courseItem->attr('data-status');
|
||||
$course->course_id = $courseItem->attr('data-id');
|
||||
$course->numberofchapters = $courseItem->attr('data-chapter-count');
|
||||
$course->timeswatched = $courseItem->attr('data-times-watched');
|
||||
$course->published_at = $courseItem->attr('data-date') > 0 ? Carbon::createFromTimestamp(
|
||||
$courseItem->attr('data-date')
|
||||
) : null;
|
||||
$courses->add($course);
|
||||
}
|
||||
return $courses;
|
||||
}
|
||||
|
||||
public function getCourseDetails(Response $response): array
|
||||
public function getCourseDetails(Response $response, int $courseId): Collection
|
||||
{
|
||||
$document = new Document($response->getBody()->getContents());
|
||||
$info = ['chapters' => []];
|
||||
foreach ($document->find('ul.chapter-list li') as $chapter) {
|
||||
$info['chapters'][] = [
|
||||
'link' => $chapter->first('a')->attr('href'),
|
||||
'title' => preg_replace('/\v(?:[\v\h]+)/', '', $chapter->first('.col')->text()),
|
||||
'duration' => $chapter->first('.length-styling')->text(),
|
||||
];
|
||||
$chapters = new Collection();
|
||||
$chapterId = 0;
|
||||
foreach ($document->find('ul.chapter-list li') as $chapterItem) {
|
||||
if ($chapterItem->first('.col')) {
|
||||
$chapterId++;
|
||||
|
||||
// if(!$chapterItem->first('.length-styling')){
|
||||
// dd($chapterItem->html());
|
||||
// }
|
||||
$chapter = Chapter::firstOrNew(['course_id' => $courseId, 'order' => $chapterId]);
|
||||
$chapter->duration = $chapterItem->first('.length-styling')?->text();
|
||||
$chapter->order = $chapterId;
|
||||
$chapter->course_id = $courseId;
|
||||
$chapter->link = config('symfonycast.base_url') . $chapterItem->first('a')->attr('href');
|
||||
$chapter->video_link = config('symfonycast.base_url') . $chapterItem->first('a')->attr(
|
||||
'href'
|
||||
) . '/download/video';
|
||||
$chapter->title = preg_replace('/\v(?:[\v\h]+)/', '', $chapterItem->first('.col')->text());
|
||||
$chapter->video_size = 0;
|
||||
$chapters->add($chapter);
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
return $chapters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,39 +2,64 @@
|
||||
|
||||
namespace App\Http\SymfonyCastDl;
|
||||
|
||||
use App\Models\Chapter;
|
||||
use App\Models\Course;
|
||||
use GuzzleHttp\TransferStats;
|
||||
use DiDom\Document;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class SymfonyCastDlService
|
||||
{
|
||||
public function __construct(HtmlParser $htmlParser)
|
||||
public Client $client;
|
||||
|
||||
public function __construct(public HtmlParser $htmlParser)
|
||||
{
|
||||
$client = new Client([
|
||||
'base_uri' => "https://symfonycasts.com",
|
||||
$this->client = new Client([
|
||||
'base_uri' => config('symfonycast.base_url'),
|
||||
'cookies' => true
|
||||
]);
|
||||
|
||||
$response = $client->get('login');
|
||||
$response = $this->client->get('login');
|
||||
|
||||
|
||||
$token = $htmlParser->getCsrfToken($response);
|
||||
|
||||
$response = $client->post('login', [
|
||||
$response = $this->client->post('login', [
|
||||
'form_params' => [
|
||||
// 'email' => 'krzysiej@gmail.com',
|
||||
// 'password' => '',
|
||||
'email' => config('symfonycast.login'),
|
||||
'password' => config('symfonycast.password'),
|
||||
'_csrf_token' => $token
|
||||
],
|
||||
'on_stats' => function (TransferStats $stats) use (&$currentUrl) {
|
||||
$currentUrl = $stats->getEffectiveUri();
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
$coursePage = $client->get('courses/filtering');
|
||||
// dump($htmlParser->getCourses($coursePage));
|
||||
public function getInfo()
|
||||
{
|
||||
$coursePage = $this->client->get('courses/filtering');
|
||||
|
||||
$singleCoursePage = $client->get('screencast/api-platform');
|
||||
dd($htmlParser->getCourseDetails($singleCoursePage));
|
||||
$courses = $this->htmlParser->getCourses($coursePage);
|
||||
$courses->each(fn($course) => $course->save());
|
||||
$singleCoursePage = $this->client->get($courses[3]->link);
|
||||
/** @var Course $course */
|
||||
foreach ($courses as $course) {
|
||||
$singleCoursePage = $this->client->get($course->link);
|
||||
$chapters = $this->htmlParser->getCourseDetails($singleCoursePage, $course->id);
|
||||
$chapters->each(fn($chapter) => $this->videoSize($chapter)->save());
|
||||
}
|
||||
}
|
||||
|
||||
public function videoSize(Chapter $chapter): Chapter
|
||||
{
|
||||
try {
|
||||
$response = $this->client->head($chapter->video_link);
|
||||
if ($response->hasHeader('Content-Length')) {
|
||||
$chapter->video_size = $response->getHeader('Content-Length')[0];
|
||||
}
|
||||
} catch (\GuzzleHttp\Exception\ClientException $exception) {
|
||||
|
||||
}
|
||||
return $chapter;
|
||||
}
|
||||
}
|
||||
|
||||
36
app/Models/Chapter.php
Normal file
36
app/Models/Chapter.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $order
|
||||
* @property string $link
|
||||
* @property string $video_link
|
||||
* @property integer $video_size
|
||||
* @property string $title
|
||||
* @property string $duration
|
||||
* @property integer $course_id
|
||||
*/
|
||||
class Chapter extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'order',
|
||||
'link',
|
||||
'video_link',
|
||||
'video_size',
|
||||
'title',
|
||||
'duration',
|
||||
'course_id',
|
||||
];
|
||||
|
||||
public function course(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Course::class);
|
||||
}
|
||||
}
|
||||
42
app/Models/Course.php
Normal file
42
app/Models/Course.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Traits\Date;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $course_id
|
||||
* @property string $name
|
||||
* @property string $thumbnail
|
||||
* @property string $link
|
||||
* @property string $status
|
||||
* @property integer $numberofchapters
|
||||
* @property integer $timeswatched
|
||||
* @property Date $published_at
|
||||
*/
|
||||
class Course extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'thumbnail',
|
||||
'link',
|
||||
'status',
|
||||
'numberofchapters',
|
||||
'timeswatched',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'published_at'
|
||||
];
|
||||
|
||||
public function chapters(): HasMany
|
||||
{
|
||||
return $this->hasMany(Chapter::class);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"laravel/tinker": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.7",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.0.1",
|
||||
|
||||
152
composer.lock
generated
152
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6969489719f565e06eb44575a4396934",
|
||||
"content-hash": "e81b030ec4951ada3e6f11c989d631ea",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -5244,6 +5244,90 @@
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "barryvdh/laravel-debugbar",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-debugbar.git",
|
||||
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/3372ed65e6d2039d663ed19aa699956f9d346271",
|
||||
"reference": "3372ed65e6d2039d663ed19aa699956f9d346271",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/routing": "^7|^8|^9",
|
||||
"illuminate/session": "^7|^8|^9",
|
||||
"illuminate/support": "^7|^8|^9",
|
||||
"maximebf/debugbar": "^1.17.2",
|
||||
"php": ">=7.2.5",
|
||||
"symfony/finder": "^5|^6"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.3.3",
|
||||
"orchestra/testbench-dusk": "^5|^6|^7",
|
||||
"phpunit/phpunit": "^8.5|^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.6-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Barryvdh\\Debugbar\\ServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Barryvdh\\Debugbar\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP Debugbar integration for Laravel",
|
||||
"keywords": [
|
||||
"debug",
|
||||
"debugbar",
|
||||
"laravel",
|
||||
"profiler",
|
||||
"webprofiler"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://fruitcake.nl",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/barryvdh",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-07-11T09:26:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
"version": "1.4.1",
|
||||
@@ -5682,6 +5766,72 @@
|
||||
},
|
||||
"time": "2022-07-21T14:33:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maximebf/debugbar",
|
||||
"version": "v1.18.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maximebf/php-debugbar.git",
|
||||
"reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6",
|
||||
"reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1|^8",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/var-dumper": "^2.6|^3|^4|^5|^6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5.20 || ^9.4.2",
|
||||
"twig/twig": "^1.38|^2.7|^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"kriswallsmith/assetic": "The best way to manage assets",
|
||||
"monolog/monolog": "Log using Monolog",
|
||||
"predis/predis": "Redis storage"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.17-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"DebugBar\\": "src/DebugBar/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maxime Bouroumeau-Fuseau",
|
||||
"email": "maxime.bouroumeau@gmail.com",
|
||||
"homepage": "http://maximebf.com"
|
||||
},
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Debug bar in the browser for php application",
|
||||
"homepage": "https://github.com/maximebf/php-debugbar",
|
||||
"keywords": [
|
||||
"debug",
|
||||
"debugbar"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maximebf/php-debugbar/issues",
|
||||
"source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0"
|
||||
},
|
||||
"time": "2021-12-27T18:49:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
"version": "1.5.0",
|
||||
|
||||
9
config/symfonycast.php
Normal file
9
config/symfonycast.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
return [
|
||||
'base_url' => env('SYMFONY_CAST_BASE_URL'),
|
||||
'login' => env('SYMFONY_CAST_LOGIN'),
|
||||
'password' => env('SYMFONY_CAST_PASSWORD'),
|
||||
];
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('password_resets', function (Blueprint $table) {
|
||||
$table->string('email')->index();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('password_resets');
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('courses', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('course_id')->unique();
|
||||
$table->text('name');
|
||||
$table->text('thumbnail');
|
||||
$table->text('link');
|
||||
$table->enum('status', ['unfinished', 'upcoming', 'complete']);
|
||||
$table->integer('numberofchapters');
|
||||
$table->integer('timeswatched');
|
||||
$table->dateTime('published_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('courses');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Course;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('chapters', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Course::class);
|
||||
$table->integer('order');
|
||||
$table->text('title');
|
||||
$table->text('duration')->nullable();
|
||||
$table->text('link');
|
||||
$table->text('video_link');
|
||||
$table->integer('video_size');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('chapters');
|
||||
}
|
||||
};
|
||||
12
resources/views/components/layout.blade.php
Normal file
12
resources/views/components/layout.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ $title ?? 'Symfonycast.local' }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
{{ $slot }}
|
||||
</body>
|
||||
</html>
|
||||
14
resources/views/course/index.blade.php
Normal file
14
resources/views/course/index.blade.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<x-layout>
|
||||
<h1>
|
||||
{{ $course->name }}
|
||||
</h1>
|
||||
|
||||
<table class="table">
|
||||
@foreach($course->chapters()->get() as $chapter)
|
||||
<tr>
|
||||
<td><a href="{{ route('course.chapter', ['chapter' => $chapter->id]) }}">{{ $chapter->title }}</a></td>
|
||||
<td>{{ $chapter->duration }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</x-layout>
|
||||
24
resources/views/index.blade.php
Normal file
24
resources/views/index.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<x-layout>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>status</th>
|
||||
<th>Chapters</th>
|
||||
<th>Published at</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@foreach($courses as $course)
|
||||
<tr>
|
||||
<td class="align-middle">{{ $course->id }}</td>
|
||||
<td><img src="{{ $course->thumbnail }}" alt="{{ $course->name }}" class="img-thumbnail" style="width: 70px;"></td>
|
||||
<td class="align-middle"><a href="{{ route('course.index', ['0' => $course->id]) }}"> {{ $course->name }}</a></td>
|
||||
<td class="align-middle">{{ $course->status }}</td>
|
||||
<td class="align-middle">{{ $course->chapters_count }} / {{ $course->numberofchapters }}</td>
|
||||
<td class="align-middle"><abbr title="{{ $course->published_at?->format('Y-m-d') }}">{{ $course->published_at?->diffForHumans() }}</abbr></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</table>
|
||||
</x-layout>
|
||||
@@ -13,6 +13,9 @@ use Illuminate\Support\Facades\Route;
|
||||
|
|
||||
*/
|
||||
|
||||
Route::get('/', [\App\Http\Controllers\Controller::class, 'index']);
|
||||
Route::get('/download', [\App\Http\Controllers\Index::class, 'download']);
|
||||
Route::get('/', [\App\Http\Controllers\Index::class, 'index']);
|
||||
Route::get('/course/{course}', [\App\Http\Controllers\CourseController::class, 'index'])->name('course.index');
|
||||
Route::get('/chapter/{chapter}', [\App\Http\Controllers\CourseController::class, 'chapter'])->name('course.chapter');
|
||||
|
||||
|
||||
|
||||
2
storage/debugbar/.gitignore
vendored
Normal file
2
storage/debugbar/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
Reference in New Issue
Block a user