Compare commits
39 commits
Author | SHA1 | Date | |
---|---|---|---|
95a50531da | |||
ae049b7966 | |||
3fe6ec2901 | |||
be77593531 | |||
9657b843fe | |||
51526c9889 | |||
e2ebd8fbfa | |||
38a0e27d56 | |||
d451b42c29 | |||
13a3de9709 | |||
c14c0149ec | |||
4818828817 | |||
8f24453eff | |||
4e8664ee31 | |||
0b80530aef | |||
1abc294930 | |||
a4e1a3328f | |||
222b1d2d7b | |||
08010fbb61 | |||
2cac6b6a90 | |||
df5bc78f2e | |||
ba56bccc35 | |||
5af0b32634 | |||
7eadbe16f5 | |||
bcf9b5c5a7 | |||
2dae6e07e7 | |||
0a8cc600fc | |||
bfa0e365f1 | |||
ebe6b72efe | |||
42dcde7fc9 | |||
438220a664 | |||
c6b07fa803 | |||
7f3b48cf89 | |||
2fcde13ef5 | |||
f1b467e4f3 | |||
8e3103b9db | |||
15d256303f | |||
f042bf2140 | |||
baeb70dd46 |
126 changed files with 4691 additions and 1140 deletions
|
@ -23,7 +23,6 @@ class LoginUser
|
|||
try {
|
||||
$user = User::where('login', $userInfo['username'])->first();
|
||||
if ($user) {
|
||||
$user['role'] = ($user->getRoleNames())->first();
|
||||
$password = $user->getAuthPassword();
|
||||
if (strlen($password) === 32 && ctype_xdigit($password)) {
|
||||
if (hash_equals($password, md5($userInfo['password']))) {
|
||||
|
|
|
@ -26,7 +26,7 @@ class UpdateUserPassword implements UpdatesUserPasswords
|
|||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
'pass' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,43 +13,48 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
// Use PHP's built-in list of timezones for robust validation
|
||||
$timezones = timezone_identifiers_list();
|
||||
|
||||
$rules = [
|
||||
'login' => ['required', 'string', 'max:255', Rule::unique('cc_subjs')->ignore($user->id)],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('cc_subjs')->ignore($user->id),
|
||||
],
|
||||
'email' => ['nullable', 'string', 'email', 'max:255', Rule::unique('cc_subjs')->ignore($user->id)],
|
||||
'first_name' => ['nullable', 'string', 'max:255'],
|
||||
'last_name' => ['nullable', 'string', 'max:255'],
|
||||
'cell_phone' => ['nullable', 'string', 'max:25'],
|
||||
'timezone' => ['nullable', 'string', Rule::in($timezones)],
|
||||
];
|
||||
|
||||
// Only add the 'type' validation rule if the user has the permission to change roles
|
||||
if (auth()->user()->hasPermissionTo('user.changeRole')) {
|
||||
if (isset($rules['type']) && auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
$rules['type'] = ['required', 'string', 'max:6', Rule::in(['admin', 'editor', 'dj'])];
|
||||
}
|
||||
|
||||
Validator::make($input, $rules)->validateWithBag('updateProfileInformation');
|
||||
|
||||
if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$data = [
|
||||
'login' => $input['login'],
|
||||
'email' => $input['email'],
|
||||
'first_name' => $input['first_name'],
|
||||
'last_name' => $input['last_name'],
|
||||
'cell_phone' => $input['cell_phone'],
|
||||
];
|
||||
|
||||
// Only update 'type' if the user has the permission
|
||||
if (auth()->user()->hasPermissionTo('user.changeRole')) {
|
||||
if ($input['email'] !== $user->email && $user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
if (isset($rules['type']) && auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
$data['type'] = $input['type'];
|
||||
}
|
||||
|
||||
$user->forceFill($data)->save();
|
||||
}
|
||||
|
||||
// The timezone is handled by the mutator in the User model
|
||||
$user->timezone = $input['timezone'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,16 +67,22 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
|||
$data = [
|
||||
'login' => $input['login'],
|
||||
'email' => $input['email'],
|
||||
'first_name' => $input['first_name'],
|
||||
'last_name' => $input['last_name'],
|
||||
'cell_phone' => $input['cell_phone'],
|
||||
'email_verified_at' => null,
|
||||
];
|
||||
|
||||
// Only update 'type' if the user has the permission
|
||||
if (auth()->user()->hasPermissionTo('user.changeRole')) {
|
||||
// Corrected permission name to be consistent
|
||||
if (auth()->user()->hasPermissionTo('users.changeRole')) {
|
||||
$data['type'] = $input['type'];
|
||||
}
|
||||
|
||||
$user->forceFill($data)->save();
|
||||
|
||||
// The timezone is handled by the mutator in the User model
|
||||
$user->timezone = $input['timezone'];
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
89
app/Console/Commands/CheckPodcastDownload.php
Normal file
89
app/Console/Commands/CheckPodcastDownload.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Podcast\CeleryTask;
|
||||
use App\Models\Podcast\PodcastEpisode;
|
||||
use App\Models\Podcast\ThirdPartyTrackReference;
|
||||
use Celery\Celery;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CheckPodcastDownload extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:check-podcast-download';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for messages in celeryresults queue to update celery_tasks table for finished tasks';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$tasks = CeleryTask::where('status','=','PENDING')->with('aThirdPartyTrackReferences')->get();
|
||||
if ($tasks->count() > 0) {
|
||||
foreach ($tasks as $task) {
|
||||
Log::info($task->task_id);
|
||||
$queue = 'celeryresults.'.$task['task_id'];
|
||||
$c = new Celery(
|
||||
host: config('rabbitmq.host'),
|
||||
login: config('rabbitmq.user'),
|
||||
password: config('rabbitmq.password'),
|
||||
vhost: config('rabbitmq.vhost'),
|
||||
exchange: 'celeryresults',
|
||||
binding: $queue,
|
||||
result_expire: 900000
|
||||
);
|
||||
|
||||
try {
|
||||
$message = $c->getAsyncResultMessage($task->name, $task->task_id);
|
||||
|
||||
if ($message) {
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$body = json_decode($message['body'], true);
|
||||
$result = json_decode($body['result'], true);
|
||||
|
||||
$podcastEpisode = PodcastEpisode::where('id', '=',$result['episodeid'])->first();
|
||||
$podcastEpisode->fill([
|
||||
'file_id' => $result['fileid'],
|
||||
])->save();
|
||||
|
||||
$timezone = new DateTimeZone(getenv('APP_TIMEZONE'));
|
||||
$thirdPartyTrackReference = ThirdPartyTrackReference::where('foreign_id','=',$podcastEpisode->id)->first();
|
||||
$thirdPartyTrackReference->fill([
|
||||
'file_id' => $result['fileid'],
|
||||
'upload_time' => new DateTime('now', $timezone)
|
||||
])->save();
|
||||
|
||||
$celeryTask = CeleryTask::where('task_id','=',$task->task_id)->where('track_reference','=',$thirdPartyTrackReference->id)->first();
|
||||
$celeryTask->fill([
|
||||
'status' => 'SUCCESS'
|
||||
])->save();
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('test '.$e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,9 @@ class Kernel extends ConsoleKernel
|
|||
// $schedule->command('inspire')->hourly();
|
||||
$schedule->command('telescope:prune --hours=48')->daily();
|
||||
$schedule->command('app:create-show-schedule')->everyMinute();
|
||||
$schedule->command('app:check-podcast-download')->everyFiveSeconds();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Enums;
|
|||
enum PlaylistContentType: int
|
||||
{
|
||||
case audioclip = 0;
|
||||
case stream = 1;
|
||||
case webstream = 1;
|
||||
case block = 2;
|
||||
|
||||
public static function fromName(string $name){
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Filters;
|
||||
|
||||
use App\Filters\FiltersType\AllFilter;
|
||||
use App\Filters\FiltersType\IsFilter;
|
||||
use App\Filters\FiltersType\LikeFilter;
|
||||
|
||||
class FileFilters
|
||||
|
|
12
app/Filters/FiltersType/GreaterEqualFilter.php
Normal file
12
app/Filters/FiltersType/GreaterEqualFilter.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters\FiltersType;
|
||||
|
||||
class GreaterEqualFilter
|
||||
{
|
||||
function __invoke($query, $whereToSearch, $textToSearch) {
|
||||
return $query->where(function($query) use ($whereToSearch, $textToSearch) {
|
||||
$query->where($whereToSearch, '>=', $textToSearch);
|
||||
});
|
||||
}
|
||||
}
|
12
app/Filters/FiltersType/SmallerEqualFiler.php
Normal file
12
app/Filters/FiltersType/SmallerEqualFiler.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters\FiltersType;
|
||||
|
||||
class SmallerEqualFiler
|
||||
{
|
||||
function __invoke($query, $whereToSearch, $textToSearch) {
|
||||
return $query->where(function($query) use ($whereToSearch, $textToSearch) {
|
||||
$query->where($whereToSearch, '<=', $textToSearch);
|
||||
});
|
||||
}
|
||||
}
|
15
app/Filters/FiltersType/SpotFilter.php
Normal file
15
app/Filters/FiltersType/SpotFilter.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters\FiltersType;
|
||||
|
||||
use Log;
|
||||
class SpotFilter
|
||||
{
|
||||
function __invoke($query, $spotTableName, $idColumnName) {
|
||||
try {
|
||||
return $query->whereIn('id', $spotTableName::select($idColumnName));
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
}
|
38
app/Filters/PodcastFilter.php
Normal file
38
app/Filters/PodcastFilter.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Filters\FiltersType\AllFilter;
|
||||
use App\Filters\FiltersType\LikeFilter;
|
||||
|
||||
class PodcastFilter
|
||||
{
|
||||
protected $filters = [
|
||||
'title' => LikeFilter::class,
|
||||
'all' => AllFilter::class,
|
||||
];
|
||||
|
||||
public function apply($query, $filters)
|
||||
{
|
||||
foreach ($this->receivedFilters($filters) as $name => $value) {
|
||||
switch ($name) {
|
||||
case 'all':
|
||||
$name = array_diff(array_keys($this->filters), ['all']);
|
||||
$filterInstance = new $this->filters['all'];
|
||||
break;
|
||||
default:
|
||||
$filterInstance = new $this->filters[$name];
|
||||
break;
|
||||
}
|
||||
$query = $filterInstance($query, $name, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
public function receivedFilters($filters)
|
||||
{
|
||||
return $filters->only(array_keys($this->filters));
|
||||
}
|
||||
}
|
42
app/Filters/ScheduleFilters.php
Normal file
42
app/Filters/ScheduleFilters.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Filters\FiltersType\AllFilter;
|
||||
use App\Filters\FiltersType\GreaterEqualFilter;
|
||||
use App\Filters\FiltersType\IsFilter;
|
||||
use App\Filters\FiltersType\LikeFilter;
|
||||
use App\Filters\FiltersType\SmallerEqualFiler;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ScheduleFilters
|
||||
{
|
||||
protected $filters = [
|
||||
'ends' => SmallerEqualFiler::class,
|
||||
'starts' => GreaterEqualFilter::class,
|
||||
];
|
||||
|
||||
public function apply($query, Request $filters)
|
||||
{
|
||||
foreach ($this->receivedFilters($filters) as $name => $value) {
|
||||
switch ($name) {
|
||||
case 'all':
|
||||
$name = array_diff(array_keys($this->filters), ['all']);
|
||||
$filterInstance = new $this->filters['all'];
|
||||
break;
|
||||
default:
|
||||
$filterInstance = new $this->filters[$name];
|
||||
break;
|
||||
}
|
||||
$query = $filterInstance($query, $name, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
public function receivedFilters(Request $filters)
|
||||
{
|
||||
return $filters->only(array_keys($this->filters));
|
||||
}
|
||||
}
|
|
@ -2,44 +2,38 @@
|
|||
|
||||
namespace App\Filters\Show;
|
||||
|
||||
use App\Filters\FiltersType\AllFilter;
|
||||
use App\Filters\FiltersType\LikeFilter;
|
||||
use App\Models\Spot\SpotPlaylist;
|
||||
|
||||
class ShowFilters
|
||||
{
|
||||
protected $filters = [
|
||||
'name' => LikeFilter::class,
|
||||
'dj' => LikeFilter::class,
|
||||
'genre' => LikeFilter::class,
|
||||
'description' => LikeFilter::class,
|
||||
'color' => LikeFilter::class,
|
||||
'background_color' => LikeFilter::class,
|
||||
'live_stream_using_airtime_auth' => LikeFilter::class,
|
||||
'live_stream_using_custom_auth' => LikeFilter::class,
|
||||
'live_stream_user' => LikeFilter::class,
|
||||
'live_stream_pass' => LikeFilter::class,
|
||||
'linked' => LikeFilter::class,
|
||||
'is_linkable' => LikeFilter::class,
|
||||
'image_path' => LikeFilter::class,
|
||||
'has_autoplaylist' => LikeFilter::class,
|
||||
'autoplaylist_id' => LikeFilter::class,
|
||||
'autoplaylist_repeat' => LikeFilter::class,
|
||||
'all' => AllFilter::class,
|
||||
];
|
||||
|
||||
public function apply($query)
|
||||
public function apply($query, $filters)
|
||||
{
|
||||
foreach ($this->receivedFilters() as $name => $value) {
|
||||
if ($name != 'per_page') {
|
||||
foreach ($this->receivedFilters($filters) as $name => $value) {
|
||||
switch ($name) {
|
||||
case 'all':
|
||||
$name = array_diff(array_keys($this->filters), ['all']);
|
||||
$filterInstance = new $this->filters['all'];
|
||||
break;
|
||||
default:
|
||||
$filterInstance = new $this->filters[$name];
|
||||
$query = $filterInstance($query, $name, $value);
|
||||
break;
|
||||
}
|
||||
$query = $filterInstance($query, $name, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
public function receivedFilters()
|
||||
public function receivedFilters($filters)
|
||||
{
|
||||
return request()->only(array_keys($this->filters));
|
||||
return $filters->only(array_keys($this->filters));
|
||||
}
|
||||
}
|
33
app/Filters/Webstream/WebstreamFilters.php
Normal file
33
app/Filters/Webstream/WebstreamFilters.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters\Webstream;
|
||||
|
||||
use App\Filters\FiltersType\LikeFilter;
|
||||
|
||||
class WebstreamFilters
|
||||
{
|
||||
protected $filters = [
|
||||
'name' => LikeFilter::class,
|
||||
'description' => LikeFilter::class,
|
||||
'url' => LikeFilter::class,
|
||||
'mime' => LikeFilter::class,
|
||||
'per_page' => null,
|
||||
];
|
||||
|
||||
public function apply($query)
|
||||
{
|
||||
foreach ($this->receivedFilters() as $name => $value) {
|
||||
if ($name != 'per_page') {
|
||||
$filterInstance = new $this->filters[$name];
|
||||
$query = $filterInstance($query, $name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function receivedFilters()
|
||||
{
|
||||
return request()->only(array_keys($this->filters));
|
||||
}
|
||||
}
|
|
@ -7,8 +7,10 @@ use App\Helpers\LengthFormatter;
|
|||
use App\Lib\RabbitMQSender;
|
||||
use App\Models\File;
|
||||
use App\Models\TrackType;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class FileController extends Controller
|
||||
|
@ -27,11 +29,17 @@ class FileController extends Controller
|
|||
$pagination = $request->per_page;
|
||||
}
|
||||
|
||||
return File::searchFilter($request)
|
||||
$files = File::searchFilter($request)
|
||||
->where('import_status', '=', 0)
|
||||
->with('track_type')
|
||||
->with('owner')
|
||||
->orderBy('artist_name')
|
||||
->with('owner');
|
||||
|
||||
if($request->track_type == 'spot'){
|
||||
$trackTypes = TrackType::where('code', '=', 'SPOT')->orWhere('parent_id', '=', '3')->pluck('id');
|
||||
$files = $files->whereIn('track_type_id', $trackTypes->toArray());
|
||||
}
|
||||
|
||||
return $files->orderBy('artist_name')
|
||||
->paginate($pagination)
|
||||
->toJson();
|
||||
}
|
||||
|
@ -48,10 +56,20 @@ class FileController extends Controller
|
|||
|
||||
$user = Auth::user();
|
||||
|
||||
$apiKey = $request->header('php-auth-user');
|
||||
|
||||
//Accept request only from logged-in users
|
||||
if (!$user) {
|
||||
if ($apiKey != 'some_secret_api_key') {
|
||||
throw new \Exception("You must be logged in");
|
||||
}
|
||||
//ToDo: check how to work in Legacy, getting user in this way is quite horrible
|
||||
try {
|
||||
$user = User::where('type','=','P')->orderBy('id','ASC')->first();
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//Mime type list: https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
||||
$request->validate([
|
||||
|
@ -132,6 +150,11 @@ class FileController extends Controller
|
|||
$fields['track_type_id'] = TrackType::where('code','MUSICA')->first()->id;
|
||||
}
|
||||
|
||||
//Force BPM to int
|
||||
if (isset($fields['bpm'])) {
|
||||
$fields['bpm'] = intval($fields['bpm']);
|
||||
}
|
||||
|
||||
//return json_encode(['req' => $fields,'id' => $id]);
|
||||
|
||||
$file->fill($fields)->save();
|
||||
|
|
10
app/Http/Controllers/ImportedPodcastController.php
Normal file
10
app/Http/Controllers/ImportedPodcastController.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ImportedPodcastController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
|
@ -8,10 +8,13 @@ use App\Models\File;
|
|||
use App\Models\Playlist;
|
||||
use App\Models\PlaylistContent;
|
||||
use App\Models\SmartBlock;
|
||||
use App\Models\Spot\SpotPlaylist;
|
||||
use App\Models\Webstream;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PlaylistController extends Controller
|
||||
{
|
||||
|
@ -21,17 +24,37 @@ class PlaylistController extends Controller
|
|||
* @return string
|
||||
*/
|
||||
public function index(Request $request) {
|
||||
try {
|
||||
if ( ! isset($request->per_page) || is_null($request)) {
|
||||
$pagination = 5;
|
||||
} else {
|
||||
$pagination = $request->per_page;
|
||||
}
|
||||
|
||||
return Playlist::searchFilter($request)
|
||||
->with(['creator', 'tracks.file', 'tracks.block', 'tracks.block.criteria', 'tracks.block.creator'])
|
||||
->orderBy('name')
|
||||
$playlists = Playlist::searchFilter($request)
|
||||
->with(
|
||||
[
|
||||
'creator',
|
||||
'tracks.file',
|
||||
'tracks.block',
|
||||
'tracks.block.criteria',
|
||||
'tracks.block.creator'
|
||||
]
|
||||
);
|
||||
|
||||
if($request->has('playlistType')) {
|
||||
$playlistType = $request->get('playlistType');
|
||||
$playlists = ($playlistType == 'show')
|
||||
? $playlists->doesntHave('spotPlaylist')
|
||||
: $playlists->has('spotPlaylist');
|
||||
}
|
||||
|
||||
return $playlists->orderBy('name')
|
||||
->paginate($pagination)
|
||||
->toJson();
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,6 +64,7 @@ class PlaylistController extends Controller
|
|||
* @throws \DateMalformedIntervalStringException
|
||||
*/
|
||||
public function store(Request $request) {
|
||||
try{
|
||||
$user = Auth::user();
|
||||
$request->validate([
|
||||
'name' => 'required',
|
||||
|
@ -54,6 +78,9 @@ class PlaylistController extends Controller
|
|||
'creator_id' => $user->id,
|
||||
'description' => $request->description,
|
||||
]);
|
||||
if($request['playlist_type'] === 'spot') {
|
||||
$dbPlaylist->spotPlaylist()->create();
|
||||
}
|
||||
// dd($request->tracks);
|
||||
foreach($request->tracks as $key => $file) {
|
||||
if (!isset($file['id'])) {
|
||||
|
@ -66,8 +93,8 @@ class PlaylistController extends Controller
|
|||
case 'block':
|
||||
$model = SmartBlock::whereId($file['id'])->first();
|
||||
break;
|
||||
case 'stream':
|
||||
//Todo: $model = Stream::whereId($file['id'])->first();
|
||||
case 'webstream':
|
||||
$model = Webstream::whereId($file['id'])->first();
|
||||
break;
|
||||
}
|
||||
$modelTime = new LengthFormatter($model->length);
|
||||
|
@ -80,7 +107,7 @@ class PlaylistController extends Controller
|
|||
'playlist_id' => $dbPlaylist->id,
|
||||
'file_id' => ($file['type'] === 'audioclip') ? $file['id'] : null,
|
||||
'block_id' => ($file['type'] === 'block') ? $file['id'] : null,
|
||||
'stream_id' => ($file['type'] === 'stream') ? $file['id'] : null,
|
||||
'stream_id' => ($file['type'] === 'webstream') ? $file['id'] : null,
|
||||
'type' => PlaylistContentType::fromName($file['type']),
|
||||
'position' => $key,
|
||||
'trackoffset' => 0, //ToDo: understand this field
|
||||
|
@ -95,6 +122,9 @@ class PlaylistController extends Controller
|
|||
$dbPlaylist->update(['length' => $length]);
|
||||
|
||||
return $dbPlaylist->with('tracks')->get()->toJson();
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +171,7 @@ class PlaylistController extends Controller
|
|||
];
|
||||
break;
|
||||
case 'stream':
|
||||
//Todo: $model = Stream::whereId($file['id'])->first();
|
||||
$model = Webstream::whereId($file['id'])->first();
|
||||
$data = [
|
||||
'playlist_id' => $playlist->id,
|
||||
'stream_id' => $file['id'],
|
||||
|
|
127
app/Http/Controllers/PodcastController.php
Normal file
127
app/Http/Controllers/PodcastController.php
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Podcast\ImportedPodcast;
|
||||
use App\Models\Podcast\Podcast;
|
||||
use App\Models\Podcast\PodcastEpisode;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PodcastController extends Controller
|
||||
{
|
||||
public function index(Request $request) {
|
||||
if (!isset($request->per_page) || is_null($request)) {
|
||||
$pagination = 5;
|
||||
} else {
|
||||
$pagination = $request->per_page;
|
||||
}
|
||||
return Podcast::searchFilter($request)
|
||||
->where('id', '!=', 1)
|
||||
->with(['episodes', 'imported', 'owner'])
|
||||
->paginate($pagination)
|
||||
->toJson();
|
||||
}
|
||||
|
||||
public function store(Request $request) {
|
||||
return $this->save($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to update a smart block
|
||||
* @param Request $request
|
||||
* @param $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(Request $request) {
|
||||
return $this->save($request);
|
||||
}
|
||||
|
||||
public function destroy($id) {
|
||||
try {
|
||||
PodcastEpisode::where('podcast_id', $id)->delete();
|
||||
ImportedPodcast::where('podcast_id', $id)->delete();
|
||||
Podcast::destroy($id);
|
||||
return true;
|
||||
} catch(\Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(Request $request, $id) {
|
||||
$xml = simplexml_load_file($request->url);
|
||||
return $xml->channel;
|
||||
}
|
||||
|
||||
public function loadPodcastDataFromXml(Request $request) {
|
||||
|
||||
try {
|
||||
$xml = simplexml_load_file($request->url, null, LIBXML_NOCDATA);
|
||||
$xmlArray = (array) $xml->channel;
|
||||
|
||||
$newArray = [];
|
||||
|
||||
foreach ($xmlArray['item'] as $key => $item) {
|
||||
$item = (array) $item;
|
||||
$newArray[$key] = $item;
|
||||
$downloading = PodcastEpisode::where('episode_guid','=',$item['guid'])->first();
|
||||
if (!is_null($downloading)) {
|
||||
$newArray[$key]['imported'] = $downloading->third_party_track_reference->celery_task->status;
|
||||
} else {
|
||||
$newArray[$key]['imported'] = 0;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$xmlArray = $newArray = false;
|
||||
}
|
||||
|
||||
return json_encode([
|
||||
'podcast' => $xmlArray,
|
||||
'episodes' => $newArray
|
||||
]);
|
||||
}
|
||||
|
||||
protected function save(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$xml = simplexml_load_file($request->url);
|
||||
$itunes = $xml->channel->children('itunes', TRUE);
|
||||
|
||||
try {
|
||||
$dbPodcast = Podcast::firstOrNew(['id' => $request->id]);
|
||||
DB::beginTransaction();
|
||||
$dbPodcast->fill([
|
||||
'url' => $request->url,
|
||||
'title' => $request->title,
|
||||
'creator' => $itunes->author,
|
||||
'description' => $itunes->subtitle,
|
||||
'language' => $xml->channel->language,
|
||||
'copyright' => $xml->channel->copyright,
|
||||
'link' => $xml->channel->link,
|
||||
'itunes_author'=> $itunes->author,
|
||||
'itunes_keywords' => '',
|
||||
'itunes_summary' => $itunes->summary,
|
||||
'itunes_subtitle' => $itunes->subtitle,
|
||||
'itunes_category' => '',
|
||||
'itunes_explicit' => $itunes->explicit,
|
||||
'owner' => $user->id,
|
||||
])->save();
|
||||
|
||||
$dbImportedPodcast = ImportedPodcast::firstOrNew(['podcast_id' => $dbPodcast->id]);;
|
||||
$dbImportedPodcast->fill([
|
||||
'auto_ingest' => !isset($request->auto_ingest) ? true : false,
|
||||
'podcast_id' => $dbPodcast->id
|
||||
])->save();
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'podcast' => $dbPodcast,
|
||||
'episodes' => $xml->channel->children('item', TRUE)
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
139
app/Http/Controllers/PodcastEpisodeController.php
Normal file
139
app/Http/Controllers/PodcastEpisodeController.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Podcast\CeleryTask;
|
||||
use App\Models\Podcast\Podcast;
|
||||
use App\Models\Podcast\PodcastEpisode;
|
||||
use App\Models\Podcast\ThirdPartyTrackReference;
|
||||
use Celery\Celery;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PodcastEpisodeController extends Controller
|
||||
{
|
||||
private static $_CELERY_MESSAGE_TIMEOUT = 900000; // 15 minutes
|
||||
|
||||
public function index(Request $request) {
|
||||
$user = Auth::user();
|
||||
try {
|
||||
if (!$user->id) {
|
||||
throw new \Exception('You must be logged in');
|
||||
}
|
||||
return PodcastEpisode::where('podcast_id','=',$request->podcast_id)->get()->toJson();
|
||||
|
||||
} catch (\Exception $exception) {
|
||||
return response($exception->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request) {
|
||||
$user = Auth::user();
|
||||
try {
|
||||
if (!$user->id) {
|
||||
throw new \Exception('You must be logged in');
|
||||
}
|
||||
|
||||
$podcastEpisode = new PodcastEpisode();
|
||||
$podcastEpisode->fill([
|
||||
'podcast_id' => $request->podcast_id,
|
||||
'publication_date' =>$request->episode['pubDate'],
|
||||
'download_url' => $request->episode['link'],
|
||||
'episode_guid' => $request->episode['guid'],
|
||||
'episode_title' => $request->episode['title'],
|
||||
'episode_description' => htmlentities($request->episode['description']),
|
||||
])->save();
|
||||
|
||||
$brokerTask = $this->downloadPodcastEpisode($request, $podcastEpisode);
|
||||
|
||||
$ref = new ThirdPartyTrackReference();
|
||||
$ref->fill([
|
||||
'service' => 'podcast',
|
||||
'foreign_id' => $podcastEpisode->id,
|
||||
'file_id' => null
|
||||
])->save();
|
||||
|
||||
$task = new CeleryTask();
|
||||
$timezone = new DateTimeZone(getenv('APP_TIMEZONE'));
|
||||
$task->fill([
|
||||
'task_id' => $brokerTask->task_id,
|
||||
'track_reference' => $ref->id,
|
||||
'name' => 'podcast-download',
|
||||
'dispatch_time' => new DateTime('now', $timezone),
|
||||
'status' => 'PENDING'
|
||||
])->save();
|
||||
|
||||
return $podcastEpisode->toJson();
|
||||
|
||||
} catch (\Exception $exception) {
|
||||
return response($exception->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPodcastEpisodeDownload($id) {
|
||||
$episode = PodcastEpisode::where('id',$id)->firstOrFail();
|
||||
return $episode->third_party_track_reference->celery_task->status;
|
||||
}
|
||||
|
||||
public function savePodcastEpisode(Request $request) {
|
||||
|
||||
if(isset($request->file)) {
|
||||
try {
|
||||
$file = (new FileController())->store($request);
|
||||
} catch (\Exception $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
if ($file) {
|
||||
return $file;
|
||||
}
|
||||
return json_encode([
|
||||
'id' => 0
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function downloadPodcastEpisode(Request $request, $podcastEpisode) {
|
||||
$request->validate([
|
||||
'podcast_id' => 'required',
|
||||
'episode_url' => 'required',
|
||||
'episode_title' => 'required',
|
||||
]);
|
||||
|
||||
try {
|
||||
$podcast = Podcast::findOrFail($request->podcast_id);
|
||||
|
||||
$conn = new Celery(
|
||||
config('rabbitmq.host'),
|
||||
config('rabbitmq.user'),
|
||||
config('rabbitmq.password'),
|
||||
config('rabbitmq.vhost'),
|
||||
'podcast',
|
||||
'podcast',
|
||||
config('rabbitmq.port'),
|
||||
false,
|
||||
self::$_CELERY_MESSAGE_TIMEOUT
|
||||
);
|
||||
|
||||
$data = [
|
||||
'episode_id' => $podcastEpisode->id,
|
||||
'episode_url' => $request->episode_url,
|
||||
'episode_title' => $podcastEpisode->episode_title,
|
||||
'podcast_name' => $podcast->title,
|
||||
'override_album' => 'false' //ToDo connect $album_override from imported_podcast,
|
||||
];
|
||||
|
||||
$taskId = $conn->PostTask('podcast-download', $data, true, 'podcast');
|
||||
return $taskId;
|
||||
|
||||
} catch (\Exception $exception) {
|
||||
Log::error($exception->getMessage());
|
||||
die($exception->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -5,12 +5,15 @@ namespace App\Http\Controllers;
|
|||
use App\Http\Requests\ScheduleRequest;
|
||||
use App\Http\Resources\ScheduleResource;
|
||||
use App\Models\Schedule;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ScheduleController extends Controller
|
||||
{
|
||||
public function index()
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ScheduleResource::collection(Schedule::all());
|
||||
$schedule = Schedule::searchFilter($request)->get();
|
||||
|
||||
return $schedule->toJson();
|
||||
}
|
||||
|
||||
public function store(ScheduleRequest $request)
|
||||
|
|
|
@ -25,15 +25,25 @@ class ShowController extends Controller
|
|||
use ShowInstancesTrait;
|
||||
use ShowDjTrait;
|
||||
|
||||
public function index(ShowFilters $filters)
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ( ! isset($filters->per_page) || is_null($filters)) {
|
||||
$pagination = 20;
|
||||
} else {
|
||||
$pagination = $filters->per_page;
|
||||
|
||||
if(isset($request->per_page) || is_null($request)) {
|
||||
$pagination = $request->per_page;
|
||||
}
|
||||
$shows = Show::searchFilter($request);
|
||||
|
||||
if($request->has('showType')) {
|
||||
$showType = $request->get('showType');
|
||||
$shows = ($showType == 'show')
|
||||
? $shows->doesntHave('spotShow')
|
||||
: $shows->has('spotShow');
|
||||
}
|
||||
|
||||
return Show::searchFilter($filters)->cursorPaginate($pagination)->toJson();
|
||||
return $shows->orderBy('name')
|
||||
->paginate($pagination)
|
||||
->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,6 +14,7 @@ use http\Exception\BadMethodCallException;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SmartBlockController extends Controller
|
||||
{
|
||||
|
@ -29,9 +30,17 @@ class SmartBlockController extends Controller
|
|||
} else {
|
||||
$pagination = $request->per_page;
|
||||
}
|
||||
$smartblocks = SmartBlock::searchFilter($request)
|
||||
->with(['creator', 'tracks', 'tracks.file', 'criteria']);
|
||||
|
||||
return SmartBlock::searchFilter($request)
|
||||
->with(['creator', 'tracks', 'tracks.file', 'criteria'])
|
||||
if($request->has('smartblockType')) {
|
||||
$smartblockType = $request->get('smartblockType');
|
||||
$smartblocks = ($smartblockType == 'show')
|
||||
? $smartblocks->doesntHave('spotSmartblock')
|
||||
: $smartblocks->has('spotSmartblock');
|
||||
}
|
||||
|
||||
return $smartblocks
|
||||
->paginate($pagination)
|
||||
->toJson();
|
||||
}
|
||||
|
@ -71,29 +80,26 @@ class SmartBlockController extends Controller
|
|||
* @return mixed string
|
||||
*/
|
||||
public function save(Request $request) {
|
||||
$user = Auth::user();
|
||||
//dd($user);
|
||||
try {
|
||||
$user = Auth::user();//dd($user);
|
||||
$request->validate([
|
||||
'name' => 'required|string',
|
||||
'type' => 'required|string',
|
||||
'criteria' => 'required|array'
|
||||
]);
|
||||
|
||||
$criteria = $this->createCriteria($request);
|
||||
$length = 0;
|
||||
|
||||
$dbSmartBlock = SmartBlock::firstOrNew(['id' => $request->id]);
|
||||
|
||||
$dbSmartBlock->fill([
|
||||
'name' => $request->name,
|
||||
'creator_id' => $user->id,
|
||||
'description' => $request->description,
|
||||
'length' => $request->length,
|
||||
])->save();
|
||||
|
||||
$this->saveCriteria($dbSmartBlock, $criteria);
|
||||
//ToDo: save content
|
||||
|
||||
if ($request['smart_block_type'] === 'spot') {
|
||||
$dbSmartBlock->spotSmartBlock()->create();
|
||||
}
|
||||
$this->saveCriteria($dbSmartBlock, $criteria);//ToDo: save content
|
||||
if (is_array($request->tracks) && count($request->tracks) > 0) {
|
||||
SmartBlockContent::where('block_id', '=', $dbSmartBlock->id)->delete();
|
||||
foreach ($request->tracks as $key => $track) {
|
||||
|
@ -102,6 +108,9 @@ class SmartBlockController extends Controller
|
|||
}
|
||||
|
||||
return $dbSmartBlock->toJson();
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,22 +2,14 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Filters\Show\ShowFilters;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ShowRequest;
|
||||
use App\Http\Resources\ShowResource;
|
||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||
use App\Models\Show\Show;
|
||||
use App\Models\User;
|
||||
use App\Traits\ScheduleTrait;
|
||||
use App\Traits\Show\ShowDaysTrait;
|
||||
use App\Traits\Show\ShowDjTrait;
|
||||
use App\Traits\Show\ShowInstancesTrait;
|
||||
use App\Traits\Show\ShowTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Exception;
|
||||
use Log;
|
||||
|
||||
# TODO Expose the show instance generation for user interaction and pypo queue
|
||||
# When pypo requests the schedule up to a certain date, generate the shows up to that date
|
||||
class UserController extends Controller
|
||||
{
|
||||
|
||||
|
@ -25,7 +17,10 @@ class UserController extends Controller
|
|||
{
|
||||
$queryParams = collect($request->except('withShow'));
|
||||
$userFilter = (new User())->searchFilter($queryParams);
|
||||
if($request->withShow) $userFilter = $userFilter->with('showDjs');
|
||||
if ($request->withShow) {
|
||||
$userFilter = $userFilter->with('showDjs');
|
||||
}
|
||||
|
||||
return response()->json($userFilter->get());
|
||||
}
|
||||
|
||||
|
@ -44,19 +39,53 @@ class UserController extends Controller
|
|||
} catch (Exception $e) {
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Show created successfully']);
|
||||
}
|
||||
|
||||
public function show(ShowResource $show)
|
||||
public function show(User $user)
|
||||
{
|
||||
return new ShowResource($show);
|
||||
$allowedRoles = ['admin', 'editor'];
|
||||
$authenticatedUser = auth()->user();
|
||||
if ( ! $authenticatedUser && ! in_array($authenticatedUser->type, $allowedRoles)) {
|
||||
return response()->json(['message' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
public function update(ShowRequest $request, Show $show)
|
||||
{
|
||||
$show->update($request->validated());
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
return new ShowResource($show);
|
||||
public function userProfile()
|
||||
{
|
||||
$user =auth()->user();
|
||||
$user->role = $user->roles()->value('name');
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user, UpdateUserProfileInformation $updater)
|
||||
{
|
||||
$authenticatedUser = auth()->user();
|
||||
|
||||
if ($authenticatedUser->id !== $user->id && !$authenticatedUser->hasPermissionTo('user.manageAll')) {
|
||||
return response()->json(['message' => 'You do not have permission to edit other users.'], 403);
|
||||
}
|
||||
if ($authenticatedUser->id === $user->id && !$authenticatedUser->hasPermissionTo('users.manageOwn')) {
|
||||
return response()->json(['message' => 'You do not have permission to edit your own profile.'], 403);
|
||||
}
|
||||
|
||||
try {
|
||||
$updater->update($user, $request->all());
|
||||
$user->load('preferences');
|
||||
|
||||
return response()->json($user);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error($e->getMessage());
|
||||
if ($e instanceof \Illuminate\Validation\ValidationException) {
|
||||
return response()->json(['message' => $e->getMessage(), 'errors' => $e->errors()], 422);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Failed to update user'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
|
@ -71,10 +100,4 @@ class UserController extends Controller
|
|||
|
||||
return response()->json(['message' => $responseMessage]);
|
||||
}
|
||||
|
||||
public function testSchedule(int $showId)
|
||||
{
|
||||
$show = Show::find($showId);
|
||||
$this->manageShowSchedule($show);
|
||||
}
|
||||
}
|
||||
|
|
154
app/Http/Controllers/WebstreamController.php
Normal file
154
app/Http/Controllers/WebstreamController.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Filters\Webstream\WebstreamFilters;
|
||||
use App\Models\Webstream;
|
||||
use App\Services\WebstreamService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class WebstreamController extends Controller
|
||||
{
|
||||
|
||||
public function __construct(WebstreamService $webstreamService)
|
||||
{
|
||||
$this->webstreamService = $webstreamService;
|
||||
}
|
||||
|
||||
public function index(WebstreamFilters $filters)
|
||||
{
|
||||
try {
|
||||
if ( ! isset($filters->per_page) || is_null($filters)) {
|
||||
$pagination = 20;
|
||||
} else {
|
||||
$pagination = $filters->per_page;
|
||||
}
|
||||
|
||||
return Webstream::searchFilter($filters)->cursorPaginate($pagination)->toJson();
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(Request $request, $webstreamId)
|
||||
{
|
||||
try {
|
||||
$webstream = Webstream::findOrFail($webstreamId);
|
||||
|
||||
return response()->json($webstream);
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => 'Webstream not found: ' . $e->getMessage()], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
try {
|
||||
# Todo length field needs to be validated against a type
|
||||
$data = $request->validate([
|
||||
'name' => ['required'],
|
||||
'description' => ['required'],
|
||||
'url' => ['required', 'url'],
|
||||
'length' => ['required'],
|
||||
]);
|
||||
|
||||
$isAudioStream = $this->webstreamService->isAudioStream($data['url']);
|
||||
|
||||
# TODO Permissions
|
||||
$data['mtime'] = now();
|
||||
$data['utime'] = now();
|
||||
$data['creator_id'] = auth()->id();
|
||||
|
||||
DB::beginTransaction();
|
||||
$webstream = Webstream::create($data);
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Webstream saved successfully!',
|
||||
'data' => $webstream,
|
||||
'isAudioStream' => $isAudioStream
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Request $request, Webstream $webstream)
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'name' => ['required'],
|
||||
'description' => ['required'],
|
||||
'url' => ['required', 'url'],
|
||||
]);
|
||||
|
||||
$isAudioStream = $this->webstreamService->isAudioStream($data['url']);
|
||||
|
||||
// Update utime to current time
|
||||
$data['utime'] = now();
|
||||
|
||||
DB::beginTransaction();
|
||||
$webstream->update($data);
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Webstream updated successfully!',
|
||||
'data' => $webstream,
|
||||
'isAudioStream' => $isAudioStream
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(Webstream $webstream)
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$webstream->delete();
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Webstream deleted successfully!'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
} catch (Throwable $e) {
|
||||
DB::rollBack();
|
||||
Log::error($e->getMessage());
|
||||
|
||||
return response()->json(['message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,6 @@ class Authenticate extends Middleware
|
|||
*/
|
||||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
return $request->expectsJson() ? null : route('login');
|
||||
return $request->expectsJson() ? null : '/login';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,18 @@ class LoginResponse implements LoginResponseContract
|
|||
public function toResponse($request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$user->load(['roles', 'preferences' => function ($query) {
|
||||
$query->where('keystr', 'user_timezone');
|
||||
}]);
|
||||
|
||||
$timezonePreference = $user->preferences->first();
|
||||
$user->timezone = $timezonePreference ? $timezonePreference->value : null;
|
||||
unset($user->preferences);
|
||||
|
||||
$user->role = $user->roles->first() ? $user->roles->first()->name : null;
|
||||
unset($user->roles);
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
}
|
||||
|
|
20
app/Http/Responses/LogoutResponse.php
Normal file
20
app/Http/Responses/LogoutResponse.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Responses;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Laravel\Fortify\Contracts\LogoutResponse as LogoutResponseContract;
|
||||
|
||||
class LogoutResponse implements LogoutResponseContract
|
||||
{
|
||||
/**
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function toResponse($request)
|
||||
{
|
||||
return $request->wantsJson()
|
||||
? new JsonResponse('', 204)
|
||||
: redirect('/login');
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Filters\PlaylistFilter;
|
||||
use App\Models\Spot\SpotPlaylist;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use MusicBrainz\Value\Track;
|
||||
|
@ -30,6 +31,10 @@ class Playlist extends Model
|
|||
return $this->hasMany(PlaylistContent::class);
|
||||
}
|
||||
|
||||
public function spotPlaylist(){
|
||||
return $this->hasOne(SpotPlaylist::class, 'playlist_id');
|
||||
}
|
||||
|
||||
public function scopeSearchFilter($query, $request) {
|
||||
$filters = new PlaylistFilter();
|
||||
return $filters->apply($query, $request);
|
||||
|
|
|
@ -40,7 +40,7 @@ class PlaylistContent extends Model
|
|||
return $this->belongsTo(SmartBlock::class, 'block_id');
|
||||
}
|
||||
|
||||
public function stream() {
|
||||
//ToDo create belongsTo relationship after create stream model
|
||||
public function webstream() {
|
||||
return $this->belongsTo(Webstream::class, 'stream_id');
|
||||
}
|
||||
}
|
||||
|
|
27
app/Models/Podcast/CeleryTask.php
Normal file
27
app/Models/Podcast/CeleryTask.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CeleryTask extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'celery_tasks';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'task_id',
|
||||
'track_reference',
|
||||
'name',
|
||||
'dispatch_time',
|
||||
'status'
|
||||
];
|
||||
|
||||
public function aThirdPartyTrackReferences() {
|
||||
return $this->hasOne(ThirdPartyTrackReference::class, 'id', 'track_reference');
|
||||
}
|
||||
}
|
25
app/Models/Podcast/ImportedPodcast.php
Normal file
25
app/Models/Podcast/ImportedPodcast.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ImportedPodcast extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'imported_podcast';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'auto_ingest',
|
||||
'auto_ingest_timestamp',
|
||||
'album_override',
|
||||
'podcast_id'
|
||||
];
|
||||
|
||||
public function podcast() {
|
||||
return $this->belongsTo(Podcast::class);
|
||||
}
|
||||
}
|
52
app/Models/Podcast/Podcast.php
Normal file
52
app/Models/Podcast/Podcast.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use App\Filters\PodcastFilter;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Podcast extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'podcast';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'url',
|
||||
'title',
|
||||
'creator',
|
||||
'description',
|
||||
'language',
|
||||
'copyright',
|
||||
'link',
|
||||
'itunes_author',
|
||||
'itunes_keywords',
|
||||
'itunes_summary',
|
||||
'itunes_subtitle',
|
||||
'itunes_category',
|
||||
'itunes_explicit',
|
||||
'owner',
|
||||
];
|
||||
|
||||
|
||||
public function owner() {
|
||||
return $this->belongsTo(User::class, 'owner');
|
||||
}
|
||||
|
||||
public function episodes() {
|
||||
return $this->hasMany(PodcastEpisode::class, 'podcast_id');
|
||||
}
|
||||
|
||||
public function imported()
|
||||
{
|
||||
return $this->hasOne(ImportedPodcast::class, 'podcast_id');
|
||||
}
|
||||
|
||||
public function scopeSearchFilter($query, $request) {
|
||||
$filters = new PodcastFilter();
|
||||
return $filters->apply($query, $request);
|
||||
}
|
||||
}
|
36
app/Models/Podcast/PodcastEpisode.php
Normal file
36
app/Models/Podcast/PodcastEpisode.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PodcastEpisode extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'podcast_episodes';
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'file_id',
|
||||
'podcast_id',
|
||||
'publication_date',
|
||||
'download_url',
|
||||
'episode_guid',
|
||||
'episode_title',
|
||||
'episode_description',
|
||||
];
|
||||
|
||||
public function file() {
|
||||
return $this->hasOne(File::class);
|
||||
}
|
||||
public function podcast() {
|
||||
return $this->belongsTo(Podcast::class);
|
||||
}
|
||||
|
||||
public function third_party_track_reference() {
|
||||
return $this->hasOne(ThirdPartyTrackReference::class, 'foreign_id', 'id');
|
||||
}
|
||||
}
|
36
app/Models/Podcast/ThirdPartyTrackReference.php
Normal file
36
app/Models/Podcast/ThirdPartyTrackReference.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ThirdPartyTrackReference extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'third_party_track_references';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'service',
|
||||
'foreign_id',
|
||||
'file_id',
|
||||
'upload_time',
|
||||
'status'
|
||||
];
|
||||
|
||||
public function file() {
|
||||
return $this->hasOne(File::class);
|
||||
}
|
||||
|
||||
public function podcast_episode() {
|
||||
return $this->belongsTo(PodcastEpisode::class, 'foreign_id');
|
||||
}
|
||||
|
||||
public function celery_task() {
|
||||
return $this->hasOne(CeleryTask::class, 'track_reference');
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Filters\ScheduleFilters;
|
||||
use App\Models\ShowInstances\ShowInstances;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
@ -25,6 +26,7 @@ class Schedule extends Model
|
|||
'broadcasted',
|
||||
'position',
|
||||
'file_id',
|
||||
'stream_id',
|
||||
'instance_id'
|
||||
];
|
||||
|
||||
|
@ -34,13 +36,23 @@ class Schedule extends Model
|
|||
'media_item_played' => 'boolean',
|
||||
];
|
||||
|
||||
public function ccFile(): BelongsTo
|
||||
public function file(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(File::class, 'cc_file_id');
|
||||
return $this->belongsTo(File::class, 'file_id');
|
||||
}
|
||||
|
||||
public function ccShowInstance(): BelongsTo
|
||||
public function webstream(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShowInstances::class, 'cc_show_instance_id');
|
||||
return $this->belongsTo(Webstream::class, 'stream_id');
|
||||
}
|
||||
|
||||
public function showInstance(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ShowInstances::class, 'show_instance_id');
|
||||
}
|
||||
public function scopeSearchFilter($query, $request)
|
||||
{
|
||||
$filters = new ScheduleFilters();
|
||||
return $filters->apply($query, $request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
namespace App\Models\Show;
|
||||
|
||||
use App\Filters\Show\ShowFilters;
|
||||
use App\Models\Playlist;
|
||||
use App\Models\ShowInstances\ShowInstances;
|
||||
use App\Models\SmartBlock;
|
||||
use App\Models\Spot\SpotShow;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Show extends Model
|
||||
{
|
||||
|
@ -66,13 +66,18 @@ class Show extends Model
|
|||
return $this->hasMany(ShowInstances::class, 'show_id');
|
||||
}
|
||||
|
||||
public function scopeSearchFilter($query, $filters)
|
||||
public function scopeSearchFilter($query, $request)
|
||||
{
|
||||
return $filters->apply($query);
|
||||
$filters = new ShowFilters();
|
||||
return $filters->apply($query, $request);
|
||||
}
|
||||
|
||||
public function playlist()
|
||||
{
|
||||
return $this->belongsTo(Playlist::class, 'autoplaylist_id', 'id');
|
||||
}
|
||||
|
||||
public function spotShow(){
|
||||
return $this->hasOne(SpotShow::class, 'show_id');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Filters\PlaylistFilter;
|
||||
use App\Models\Spot\SpotSmartBlock;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
@ -41,4 +42,8 @@ class SmartBlock extends Model
|
|||
$filters = new PlaylistFilter();
|
||||
return $filters->apply($query, $request);
|
||||
}
|
||||
|
||||
public function spotSmartBlock(){
|
||||
return $this->hasOne(SpotSmartBlock::class, 'block_id');
|
||||
}
|
||||
}
|
||||
|
|
23
app/Models/Spot/SpotPlaylist.php
Normal file
23
app/Models/Spot/SpotPlaylist.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Spot;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class SpotPlaylist extends Model
|
||||
{
|
||||
protected $table = 'wa_spot_playlist';
|
||||
|
||||
protected $fillable = [
|
||||
'playlist_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the playlist associated with this spot.
|
||||
*/
|
||||
public function playlist(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Playlist::class, 'playlist_id');
|
||||
}
|
||||
}
|
24
app/Models/Spot/SpotShow.php
Normal file
24
app/Models/Spot/SpotShow.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Spot;
|
||||
|
||||
use App\Models\Show\Show;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class SpotShow extends Model
|
||||
{
|
||||
protected $table = 'wa_spot_show';
|
||||
|
||||
protected $fillable = [
|
||||
'show_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the show associated with this spot.
|
||||
*/
|
||||
public function show(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Show::class, 'show_id');
|
||||
}
|
||||
}
|
24
app/Models/Spot/SpotSmartBlock.php
Normal file
24
app/Models/Spot/SpotSmartBlock.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Spot;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use PhpParser\Node\Stmt\Block;
|
||||
|
||||
class SpotSmartBlock extends Model
|
||||
{
|
||||
protected $table = 'wa_spot_block';
|
||||
|
||||
protected $fillable = [
|
||||
'block_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the block associated with this spot.
|
||||
*/
|
||||
public function block(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Block::class, 'block_id');
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace App\Models;
|
|||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Filters\UserFilters;
|
||||
use App\Helpers\Preferences;
|
||||
use App\Models\Show\ShowHosts;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
@ -26,10 +27,16 @@ class User extends Authenticatable
|
|||
protected $fillable = [
|
||||
'login',
|
||||
'email',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'cell_phone',
|
||||
'pass',
|
||||
'type'
|
||||
'type',
|
||||
'timezone',
|
||||
];
|
||||
|
||||
protected $appends = ['timezone'];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
|
@ -87,6 +94,24 @@ class User extends Authenticatable
|
|||
parent::setAttribute($key, $value);
|
||||
}
|
||||
|
||||
public function getTimezoneAttribute(): string
|
||||
{
|
||||
// Find the timezone preference or return a default value.
|
||||
return $this->preferences()->where('keystr', 'user_timezone')->first()->valstr ?? Preferences::getDefaultTimeZone();
|
||||
}
|
||||
|
||||
public function setTimezoneAttribute(?string $value): void
|
||||
{
|
||||
if ($value) {
|
||||
$this->preferences()->updateOrCreate(
|
||||
['keystr' => 'user_timezone'],
|
||||
['valstr' => $value]
|
||||
);
|
||||
} else {
|
||||
$this->preferences()->where('keystr', 'user_timezone')->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the password column for authentication.
|
||||
*/
|
||||
|
@ -113,4 +138,9 @@ class User extends Authenticatable
|
|||
{
|
||||
return $this->hasMany(ShowHosts::class, 'subjs_id');
|
||||
}
|
||||
|
||||
public function preferences()
|
||||
{
|
||||
return $this->hasMany(Preference::class, 'subjid');
|
||||
}
|
||||
}
|
||||
|
|
35
app/Models/Webstream.php
Normal file
35
app/Models/Webstream.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Webstream extends Model
|
||||
{
|
||||
public $timestamps = false;
|
||||
|
||||
protected $table = 'cc_webstream';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'url',
|
||||
'length',
|
||||
'creator_id',
|
||||
'mtime',
|
||||
'utime',
|
||||
'lptime',
|
||||
'mime',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'mtime' => 'timestamp',
|
||||
'utime' => 'timestamp',
|
||||
'lptime' => 'timestamp',
|
||||
];
|
||||
|
||||
public function scopeSearchFilter($query, $filters)
|
||||
{
|
||||
return $filters->apply($query);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ use Illuminate\Support\ServiceProvider;
|
|||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\Fortify;
|
||||
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
|
||||
use Laravel\Fortify\Contracts\LogoutResponse;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -32,11 +33,20 @@ class FortifyServiceProvider extends ServiceProvider
|
|||
public function boot(): void
|
||||
{
|
||||
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
|
||||
$this->app->singleton(LogoutResponse::class, LogoutResponse::class);
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
|
||||
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
|
||||
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||
Fortify::authenticateUsing([LoginUser::class, 'login']);
|
||||
$this->app->instance(LogoutResponse::class, new class implements LogoutResponse {
|
||||
public function toResponse($request)
|
||||
{
|
||||
return redirect('/login');
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())) . '|' . $request->ip());
|
||||
|
|
|
@ -50,15 +50,14 @@ class ScheduleService
|
|||
->where('ends', '<=', $showInstance->ends)
|
||||
->get()
|
||||
->keyBy(function ($item) {
|
||||
return $item->starts->timestamp . '_' . $item->ends->timestamp . '_' . $item->file_id;
|
||||
return $item->starts->timestamp . '_' . $item->ends->timestamp;
|
||||
});
|
||||
|
||||
// Compare and insert only new entries
|
||||
$newItems = collect($this->prepareScheduleData($scheduleItems))
|
||||
->filter(function ($item) use ($existingSchedule) {
|
||||
$key = Carbon::parse($item['starts'])->timestamp . '_' .
|
||||
Carbon::parse($item['ends'])->timestamp . '_' .
|
||||
$item['file_id'];
|
||||
Carbon::parse($item['ends'])->timestamp;
|
||||
return !$existingSchedule->has($key);
|
||||
})
|
||||
->toArray();
|
||||
|
@ -117,7 +116,7 @@ class ScheduleService
|
|||
$nextContent = $playlistContent[$playlistIndex];
|
||||
$contentType = $this->resolveContentType($nextContent->type);
|
||||
$content = $nextContent->$contentType()->first();
|
||||
$nextLength = LengthFormatter::contentLengthToSeconds($content->length);
|
||||
//$nextLength = LengthFormatter::contentLengthToSeconds($content->length);
|
||||
|
||||
$result = $this->managePlaylistContentEntry(
|
||||
$playlistContent[$playlistIndex],
|
||||
|
@ -153,19 +152,29 @@ class ScheduleService
|
|||
$contentLength = LengthFormatter::contentLengthToSeconds($content->length);
|
||||
$contentFinalLength = $this->calculateFinalLength($currentTime, $contentLength, $endTime);
|
||||
|
||||
$fileId = null;
|
||||
$webstreamId = null;
|
||||
|
||||
if ($contentType === 'file') {
|
||||
$content->update(['is_scheduled' => true]);
|
||||
$fileId = $content->id;
|
||||
}
|
||||
|
||||
if ($contentType === 'webstream') {
|
||||
$webstreamId = $content->id;
|
||||
}
|
||||
|
||||
|
||||
$schedule = $this->createScheduleItem(
|
||||
$currentTime,
|
||||
$contentLength,
|
||||
$contentFinalLength,
|
||||
$playlistContentEntry,
|
||||
$content->id,
|
||||
$fileId,
|
||||
$webstreamId,
|
||||
$showInstanceId
|
||||
);
|
||||
|
||||
if ($contentType === 'file') {
|
||||
$content->update(['is_scheduled' => true]);
|
||||
}
|
||||
|
||||
return [
|
||||
'currentTime' => $currentTime->addSeconds($contentLength),
|
||||
'schedule' => $schedule
|
||||
|
@ -176,7 +185,7 @@ class ScheduleService
|
|||
{
|
||||
return match (PlaylistContentType::from($type)) {
|
||||
PlaylistContentType::audioclip => 'file',
|
||||
PlaylistContentType::stream => 'stream',
|
||||
PlaylistContentType::webstream => 'webstream',
|
||||
PlaylistContentType::block => 'block',
|
||||
default => throw new InvalidArgumentException('Invalid content type'),
|
||||
};
|
||||
|
@ -194,7 +203,8 @@ class ScheduleService
|
|||
int $contentLength,
|
||||
int $contentDuration,
|
||||
PlaylistContent $playlistContentEntry,
|
||||
int $contentId,
|
||||
int | null $fileId,
|
||||
int | null $webstreamId,
|
||||
int $showInstanceId
|
||||
): Schedule {
|
||||
return new Schedule([
|
||||
|
@ -209,7 +219,8 @@ class ScheduleService
|
|||
'playout_status' => 0,
|
||||
'broadcasted' => self::BROADCAST_STATUS_PENDING,
|
||||
'position' => $playlistContentEntry->position,
|
||||
'file_id' => $contentId,
|
||||
'file_id' => $fileId,
|
||||
'stream_id' => $webstreamId,
|
||||
'instance_id' => $showInstanceId
|
||||
]);
|
||||
}
|
||||
|
@ -219,7 +230,7 @@ class ScheduleService
|
|||
return collect($scheduleItems)->map(fn (Schedule $schedule) => $schedule->only([
|
||||
'starts', 'ends', 'clip_length', 'fade_in', 'fade_out',
|
||||
'cue_in', 'cue_out', 'media_item_played', 'playout_status',
|
||||
'broadcasted', 'position', 'instance_id', 'file_id'
|
||||
'broadcasted', 'position', 'instance_id', 'file_id', 'stream_id'
|
||||
]))->toArray();
|
||||
}
|
||||
}
|
65
app/Services/WebstreamService.php
Normal file
65
app/Services/WebstreamService.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use Exception;
|
||||
|
||||
class WebstreamService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $headers
|
||||
* @param $headerAttribute
|
||||
* @param $acceptedValues
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function checkHttpHeaders($headers, $headerAttribute, $acceptedValues): bool
|
||||
{
|
||||
if (isset($headers[$headerAttribute])) {
|
||||
$contentType = is_array($headers[$headerAttribute])
|
||||
? $headers[$headerAttribute][0]
|
||||
: $headers[$headerAttribute];
|
||||
|
||||
$contentType = strtolower($contentType);
|
||||
foreach ($acceptedValues as $acceptedValue) {
|
||||
if (str_contains($contentType, $acceptedValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL is a valid audio stream
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return array{server: bool, audio: bool, streamingServer: bool}
|
||||
*/
|
||||
public function isAudioStream(string $url): array
|
||||
{
|
||||
$acceptedContentTypes = ['mpeg', 'mp3', 'aac', 'ogg'];
|
||||
$acceptedServers = ['icecast', 'shoutcast', 'rocket', 'rsas', 'azura', 'ampache'];
|
||||
$audioCheckResults = ['server' => false, 'audio' => false, 'streamingServer' => false];
|
||||
|
||||
try {
|
||||
$headers = get_headers($url, 1);
|
||||
|
||||
if ( ! $headers) {
|
||||
return $audioCheckResults;
|
||||
}
|
||||
|
||||
$audioCheckResults['server'] = true;
|
||||
$audioCheckResults['audio'] = $this->checkHttpHeaders($headers,'Content-Type',$acceptedContentTypes);
|
||||
$audioCheckResults['streamingServer'] = $this->checkHttpHeaders($headers,'Server',$acceptedServers);
|
||||
|
||||
return $audioCheckResults;
|
||||
} catch (Exception $e) {
|
||||
return $audioCheckResults;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,8 +77,11 @@ trait ShowInstancesTrait
|
|||
public function manageShowInstances(Show $show)
|
||||
{
|
||||
try {
|
||||
$generationLimitDate = Preference::where('keystr', 'shows_populated_until')->value('valstr');
|
||||
$generationLimitDate = Carbon::createFromFormat('Y-m-d H:i:s', $generationLimitDate);
|
||||
$generationLimitDateSetting = Preference::where('keystr', 'shows_populated_until')->value('valstr');
|
||||
if (empty($generationLimitDateSetting)) {
|
||||
$generationLimitDateSetting = Carbon::now()->addYears(3)->format('Y-m-d H:i:s');
|
||||
}
|
||||
$generationLimitDate = Carbon::createFromFormat('Y-m-d H:i:s', $generationLimitDateSetting);
|
||||
$showInstances = [];
|
||||
foreach ($show->showDays as $showDay) {
|
||||
try {
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Http\Resources\ShowResource;
|
|||
use App\Lib\RabbitMQSender;
|
||||
use App\Models\Show\Show;
|
||||
use App\Models\Show\ShowHosts;
|
||||
use App\Models\Spot\SpotShow;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
@ -31,6 +32,10 @@ trait ShowTrait
|
|||
}
|
||||
$this->manageShowInstances($show);
|
||||
$show->save();
|
||||
## TODO Add show to table of spots
|
||||
if($showData['show_type'] === 'spot') {
|
||||
$show->spotShow()->create();
|
||||
}
|
||||
DB::commit();
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
|
|
|
@ -18,8 +18,15 @@
|
|||
"mxl/laravel-job": "^1.6",
|
||||
"php-amqplib/php-amqplib": "^3.7",
|
||||
"spatie/laravel-permission": "^6.13",
|
||||
"xenos/musicbrainz": "^1.0"
|
||||
"xenos/musicbrainz": "^1.0",
|
||||
"libretime/celery-php": "dev-main"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/libretime/celery-php.git"
|
||||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/breeze": "*",
|
||||
|
|
|
@ -29,6 +29,6 @@ return [
|
|||
|
||||
'max_age' => 0,
|
||||
|
||||
'supports_credentials' => false,
|
||||
'supports_credentials' => true,
|
||||
|
||||
];
|
||||
|
|
|
@ -6,23 +6,36 @@ use Illuminate\Support\Facades\Schema;
|
|||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('cc_subjs', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('cc_subjs', 'remember_token')) {
|
||||
$table->rememberToken();
|
||||
}
|
||||
if (!Schema::hasColumn('cc_subjs', 'created_at') && !Schema::hasColumn('cc_subjs', 'updated_at')) {
|
||||
$table->timestamps();
|
||||
}
|
||||
if (!Schema::hasColumn('cc_subjs', 'email_verified_at')) {
|
||||
$table->string('email_verified_at')->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cc_subjs');
|
||||
Schema::table('cc_subjs', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('cc_subjs', 'remember_token')) {
|
||||
$table->dropColumn('remember_token');
|
||||
}
|
||||
if (Schema::hasColumn('cc_subjs', 'created_at')) {
|
||||
$table->dropColumn('created_at');
|
||||
}
|
||||
if (Schema::hasColumn('cc_subjs', 'updated_at')) {
|
||||
$table->dropColumn('updated_at');
|
||||
}
|
||||
if (Schema::hasColumn('cc_subjs', 'email_verified_at')) {
|
||||
$table->dropColumn('email_verified_at');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -11,6 +11,7 @@ return new class extends Migration
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if ( ! Schema::hasTable('wa_password_reset_tokens')) {
|
||||
Schema::create('wa_password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
|
@ -18,6 +19,8 @@ return new class extends Migration
|
|||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
|
|
|
@ -21,6 +21,7 @@ return new class extends Migration
|
|||
{
|
||||
$schema = Schema::connection($this->getConnection());
|
||||
|
||||
if (!$schema->hasTable('telescope_entries')) {
|
||||
$schema->create('telescope_entries', function (Blueprint $table) {
|
||||
$table->bigIncrements('sequence');
|
||||
$table->uuid('uuid');
|
||||
|
@ -37,7 +38,9 @@ return new class extends Migration
|
|||
$table->index('created_at');
|
||||
$table->index(['type', 'should_display_on_index']);
|
||||
});
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('telescope_entries_tags')) {
|
||||
$schema->create('telescope_entries_tags', function (Blueprint $table) {
|
||||
$table->uuid('entry_uuid');
|
||||
$table->string('tag');
|
||||
|
@ -50,11 +53,14 @@ return new class extends Migration
|
|||
->on('telescope_entries')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
if (!$schema->hasTable('telescope_monitoring')) {
|
||||
$schema->create('telescope_monitoring', function (Blueprint $table) {
|
||||
$table->string('tag')->primary();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
|
|
|
@ -11,6 +11,7 @@ return new class extends Migration
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (!Schema::hasTable('wa_failed_jobs')) {
|
||||
Schema::create('wa_failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
|
@ -21,6 +22,7 @@ return new class extends Migration
|
|||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
|
|
|
@ -1,33 +1,136 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('wa_personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
if (empty($tableNames)) {
|
||||
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
}
|
||||
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
|
||||
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
}
|
||||
|
||||
if (!Schema::hasTable($tableNames['permissions'])) {
|
||||
Schema::create($tableNames['permissions'], function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
if (!Schema::hasTable($tableNames['roles'])) {
|
||||
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
|
||||
$table->bigIncrements('id');
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasTable($tableNames['model_has_permissions'])) {
|
||||
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id')
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasTable($tableNames['model_has_roles'])) {
|
||||
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id')
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!Schema::hasTable($tableNames['role_has_permissions'])) {
|
||||
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id')
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id')
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
}
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('wa_personal_access_tokens');
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
if (empty($tableNames)) {
|
||||
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
}
|
||||
|
||||
Schema::dropIfExists($tableNames['role_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['model_has_roles']);
|
||||
Schema::dropIfExists($tableNames['model_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['roles']);
|
||||
Schema::dropIfExists($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,22 +4,23 @@ use Illuminate\Database\Migrations\Migration;
|
|||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if ( ! Schema::hasTable('laravelsessions')) {
|
||||
Schema::create('laravelsessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->unsignedBigInteger('user_id')->nullable();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
$table->text('payload');
|
||||
$table->integer('last_activity');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
|
|
|
@ -11,18 +11,27 @@ return new class extends Migration
|
|||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (!Schema::hasColumn('cc_track_types', 'parent_id')) {
|
||||
Schema::table('cc_track_types', function (Blueprint $table) {
|
||||
$table->foreignId('parent_id')->after('id')->nullable()->references('id')->on('cc_track_types');
|
||||
$table->foreignId('parent_id')
|
||||
->after('id')
|
||||
->nullable()
|
||||
->constrained('cc_track_types')
|
||||
->onDelete('cascade'); // optional, add if you want cascade delete
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
if (Schema::hasColumn('cc_track_types', 'parent_id')) {
|
||||
Schema::table('cc_track_types', function (Blueprint $table) {
|
||||
$table->dropForeign(['parent_id']); // drop foreign key first
|
||||
$table->dropColumn('parent_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,11 +4,7 @@ use Illuminate\Support\Facades\Schema;
|
|||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
|
@ -18,31 +14,36 @@ return new class extends Migration
|
|||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
if (empty($tableNames)) {
|
||||
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw new \Exception(
|
||||
'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'
|
||||
);
|
||||
}
|
||||
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
|
||||
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw new \Exception(
|
||||
'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! Schema::hasTable($tableNames['permissions'])) {
|
||||
Schema::create($tableNames['permissions'], function (Blueprint $table) {
|
||||
//$table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // permission id
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
}
|
||||
|
||||
if ( ! Schema::hasTable($tableNames['roles'])) {
|
||||
Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) {
|
||||
//$table->engine('InnoDB');
|
||||
$table->bigIncrements('id'); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->bigIncrements('id');
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
|
@ -50,91 +51,124 @@ return new class extends Migration
|
|||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
if ( ! Schema::hasTable($tableNames['model_has_permissions'])) {
|
||||
Schema::create(
|
||||
$tableNames['model_has_permissions'],
|
||||
function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->references('id')
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
$table->primary(
|
||||
[
|
||||
$columnNames['team_foreign_key'],
|
||||
$pivotPermission,
|
||||
$columnNames['model_morph_key'],
|
||||
'model_type'
|
||||
],
|
||||
'model_has_permissions_permission_model_type_primary'
|
||||
);
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
if ( ! Schema::hasTable($tableNames['model_has_roles'])) {
|
||||
Schema::create(
|
||||
$tableNames['model_has_roles'],
|
||||
function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->references('id')
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
$table->primary(
|
||||
[
|
||||
$columnNames['team_foreign_key'],
|
||||
$pivotRole,
|
||||
$columnNames['model_morph_key'],
|
||||
'model_type'
|
||||
],
|
||||
'model_has_roles_role_model_type_primary'
|
||||
);
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
if ( ! Schema::hasTable($tableNames['role_has_permissions'])) {
|
||||
Schema::create(
|
||||
$tableNames['role_has_permissions'],
|
||||
function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->references('id')
|
||||
->on($tableNames['permissions'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->references('id')
|
||||
->on($tableNames['roles'])
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
$table->primary([$pivotPermission, $pivotRole],
|
||||
'role_has_permissions_permission_id_role_id_primary');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
if (empty($tableNames)) {
|
||||
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
throw new \Exception(
|
||||
'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'
|
||||
);
|
||||
}
|
||||
|
||||
Schema::drop($tableNames['role_has_permissions']);
|
||||
Schema::drop($tableNames['model_has_roles']);
|
||||
Schema::drop($tableNames['model_has_permissions']);
|
||||
Schema::drop($tableNames['roles']);
|
||||
Schema::drop($tableNames['permissions']);
|
||||
Schema::dropIfExists($tableNames['role_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['model_has_roles']);
|
||||
Schema::dropIfExists($tableNames['model_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['roles']);
|
||||
Schema::dropIfExists($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWaSpotPlaylistTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Schema::hasTable('wa_spot_playlist')) {
|
||||
Schema::create('wa_spot_playlist', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('playlist_id');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('playlist_id')->references('id')->on('cc_playlist')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('wa_spot_playlist');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWaSpotShowTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Schema::hasTable('wa_spot_show')) {
|
||||
Schema::create('wa_spot_show', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('show_id');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('show_id')->references('id')->on('cc_show')->onDelete('cascade');
|
||||
});
|
||||
}}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('wa_spot_show');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateWaSpotBlockTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Schema::hasTable('wa_spot_block')) {
|
||||
Schema::create('wa_spot_block', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('block_id');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('block_id')->references('id')->on('cc_block')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('wa_spot_block');
|
||||
}
|
||||
}
|
BIN
public/imgs/sintonia-horizontal.jpg
Normal file
BIN
public/imgs/sintonia-horizontal.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
|
@ -1,3 +1,26 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
src: url('../fonts/AtkinsonHyperlegible-Regular.ttf');
|
||||
font-weight: 1 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Atkinson Hyperlegible';
|
||||
src: url('../fonts/AtkinsonHyperlegible-Bold.ttf');
|
||||
font-weight: 601 900;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F4F4EC;
|
||||
color: #141414;
|
||||
font-family: "Atkinson Hyperlegible", sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size:2.5rem;
|
||||
font-weight: bold;
|
||||
}
|
BIN
resources/fonts/AtkinsonHyperlegible-Bold.ttf
Normal file
BIN
resources/fonts/AtkinsonHyperlegible-Bold.ttf
Normal file
Binary file not shown.
BIN
resources/fonts/AtkinsonHyperlegible-BoldItalic.ttf
Normal file
BIN
resources/fonts/AtkinsonHyperlegible-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
resources/fonts/AtkinsonHyperlegible-Italic.ttf
Normal file
BIN
resources/fonts/AtkinsonHyperlegible-Italic.ttf
Normal file
Binary file not shown.
BIN
resources/fonts/AtkinsonHyperlegible-Regular.ttf
Normal file
BIN
resources/fonts/AtkinsonHyperlegible-Regular.ttf
Normal file
Binary file not shown.
|
@ -8,6 +8,12 @@ import { createPinia } from "pinia";
|
|||
import { createI18n } from "vue-i18n";
|
||||
|
||||
import App from "@/layouts/App.vue";
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
|
||||
import.meta.glob([
|
||||
'../fonts/**',
|
||||
]);
|
||||
|
||||
const pinia = createPinia();
|
||||
const i18n = createI18n(vueI18n);
|
||||
const app = createApp(App);
|
||||
|
@ -16,4 +22,8 @@ app.use(pinia)
|
|||
.use(i18n)
|
||||
.use(router)
|
||||
.use(vuetify)
|
||||
.mount("#app");
|
||||
|
||||
const auth = useAuthStore();
|
||||
auth.fetchUser().finally(() => {
|
||||
app.mount("#app");
|
||||
});
|
2
resources/js/bootstrap.js
vendored
2
resources/js/bootstrap.js
vendored
|
@ -6,7 +6,7 @@
|
|||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.withCredentials = true
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
|
|
|
@ -68,6 +68,7 @@ const confirmDelete = (confirm, bulk) => {
|
|||
})
|
||||
}
|
||||
}
|
||||
getItems(listData)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
|
@ -92,6 +93,8 @@ watch(search, (newValue, oldValue) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Archivio</h1>
|
||||
<Table
|
||||
:headers="visibleHeaders"
|
||||
v-model:selected="selected"
|
||||
|
@ -119,6 +122,7 @@ watch(search, (newValue, oldValue) => {
|
|||
<FileUpload
|
||||
v-if="dialog.type === 'upload'"
|
||||
@close-dialog="closeDialog"
|
||||
@confirm="confirmDelete"
|
||||
/>
|
||||
<FileEdit
|
||||
v-else-if="dialog.type === 'edit'"
|
||||
|
@ -135,6 +139,7 @@ watch(search, (newValue, oldValue) => {
|
|||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, onActivated, onDeactivated, ref} from "vue";
|
||||
import {computed, onActivated, onDeactivated, onMounted, ref} from "vue";
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
import {deleteShow} from "@models/show/show.ts";
|
||||
import {baseShowInstance, deleteShowInstance, getShowInstances} from "@models/show/showInstance.ts";
|
||||
|
@ -10,7 +10,7 @@ import CalendarShowEvent from "@partials/show/CalendarShowEvent.vue"
|
|||
|
||||
// Store
|
||||
const auth = useAuthStore();
|
||||
const userRole = auth.userData.user.role;
|
||||
const userRole = auth.userData.role;
|
||||
|
||||
// Data
|
||||
const editMode = ref(false);
|
||||
|
@ -76,7 +76,7 @@ const goBack = async () => {
|
|||
// so reducing the network calls sent
|
||||
// That requires the handling of the context menu
|
||||
// and the show/instance id in a different way though
|
||||
onActivated(async () => {
|
||||
onMounted(async () => {
|
||||
await triggerFetchShowInstances(new Date());
|
||||
intervalId = setInterval(async () => {
|
||||
if (!isRunning.value) {
|
||||
|
@ -91,6 +91,8 @@ onDeactivated(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Dashboard {{ (showCreateEditMode) ? ' - Crea/Modifica trasmissione' : (showInstanceCreateEditMode) ? ' - Modifica puntata' : ''}}</h1>
|
||||
<template v-if="showCreateEditMode || showInstanceCreateEditMode">
|
||||
<ShowForm v-if="showCreateEditMode" :showId="showSelected" @go-back="goBack"/>
|
||||
<ShowInstanceForm v-if="showInstanceCreateEditMode" :showInstance="selectedShowInstance"
|
||||
|
@ -114,6 +116,7 @@ onDeactivated(() => {
|
|||
@contextMenuDeleteShow="contextMenuDeleteShow"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -3,19 +3,20 @@ import {playlist_page} from "@/composables/content/playlist_page.ts";
|
|||
import Table from "@/components/content/partials/Table.vue";
|
||||
import PlaylistEditor from "@/components/content/partials/PlaylistEditor.vue";
|
||||
import ConfirmDelete from "@/components/content/partials/dialogs/ConfirmDelete.vue";
|
||||
import {reactive, ref, watch} from "vue";
|
||||
import {onBeforeMount, onMounted, type PropType, reactive, ref, watch} from "vue";
|
||||
import {usePlaylistStore} from "@/stores/playlist.store.ts";
|
||||
import {baseSmartBlock} from "@models/smartblock/smartblock.ts";
|
||||
import {basePlaylist} from "@models/playlist/playlist.ts";
|
||||
import {useShowTypeStore} from "@stores/showType.store.ts";
|
||||
|
||||
const playlistStore = usePlaylistStore();
|
||||
|
||||
const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = playlist_page();
|
||||
|
||||
// Props, data, stores
|
||||
const itemEdited = ref({
|
||||
id: null
|
||||
});
|
||||
|
||||
const bulk = ref(false)
|
||||
const dialog = reactive({
|
||||
open: false,
|
||||
|
@ -23,7 +24,9 @@ const dialog = reactive({
|
|||
title: '',
|
||||
text: ''
|
||||
})
|
||||
const showTypeStore = useShowTypeStore();
|
||||
|
||||
// Funcs
|
||||
const openDialog = (type, title = '', text = '', bulk = false) => {
|
||||
dialog.open = true
|
||||
dialog.type = type
|
||||
|
@ -37,10 +40,11 @@ const edit = (item) => {
|
|||
item = basePlaylist();
|
||||
}
|
||||
playlistStore.loadPlaylist(item);
|
||||
playlistStore.currentPlaylist.playlist_type = showTypeStore.currentType;
|
||||
itemEdited.value = item;
|
||||
}
|
||||
|
||||
const save = (item) => {
|
||||
const save = (item) => {5
|
||||
if (item.name === '') {
|
||||
//Check required fields
|
||||
console.log('error!')
|
||||
|
@ -91,10 +95,11 @@ const resetItemEdited = () => {
|
|||
watch(search, (newValue, oldValue) => {
|
||||
getItems(listData)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Playlist {{ (showTypeStore.type === 'spot') ? 'pubblicitarie' : '' }}</h1>
|
||||
<PlaylistEditor
|
||||
v-if="itemEdited.id !== null && !dialog.open"
|
||||
:item="itemEdited"
|
||||
|
@ -135,6 +140,7 @@ watch(search, (newValue, oldValue) => {
|
|||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
171
resources/js/components/content/Podcast.vue
Normal file
171
resources/js/components/content/Podcast.vue
Normal file
|
@ -0,0 +1,171 @@
|
|||
<script setup lang="ts">
|
||||
import Table from "@partials/Table.vue";
|
||||
import {usePodcastStore} from "@/stores/podcast.store.ts";
|
||||
import {podcast_page} from "@/composables/content/podcast_page.ts";
|
||||
import {basePodcast} from "@models/podcast/podcast.ts";
|
||||
import {reactive, ref} from "vue";
|
||||
import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue";
|
||||
import PodcastEditor from "@partials/PodcastEditor.vue";
|
||||
import SmartBlockEditor from "@partials/SmartBlockEditor.vue";
|
||||
|
||||
|
||||
const podcastStore = usePodcastStore();
|
||||
podcastStore.loadPodcast(basePodcast());
|
||||
const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = podcast_page();
|
||||
const url = ref('');
|
||||
const itemEdited = ref(basePodcast());
|
||||
const episodes = ref([]);
|
||||
const bulk = ref(false);
|
||||
const dialog = reactive({
|
||||
open: false,
|
||||
type: '',
|
||||
title: '',
|
||||
text: ''
|
||||
});
|
||||
const dialogLoading = ref(false);
|
||||
|
||||
const openDialog = (type, title = '', text = '', bulk = false) => {
|
||||
dialog.open = true
|
||||
dialog.type = type
|
||||
dialog.title = title
|
||||
dialog.text = text
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
openDialog(
|
||||
'add',
|
||||
'Aggiungi podcast',
|
||||
'Inserisci l\'url del feed RSS del podcast che vuoi aggiungere.'
|
||||
);
|
||||
}
|
||||
|
||||
const confirm = (confirm, bulk) => {
|
||||
switch (dialog.type) {
|
||||
case 'delete':
|
||||
confirmDelete(confirm, bulk);
|
||||
break;
|
||||
case 'add':
|
||||
confirmAdd(confirm);
|
||||
}
|
||||
}
|
||||
|
||||
const confirmAdd = async (confirm) => {
|
||||
if (confirm) {
|
||||
dialogLoading.value = true;
|
||||
await axios.get('/rss_podcast_load', {
|
||||
params: {
|
||||
url: url.value,
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.data.podcast) {
|
||||
podcastStore.updateField({key: 'title', value: res.data.podcast.title});
|
||||
podcastStore.updateField({key: 'url', value: url});
|
||||
podcastStore.currentPodcastEpisodes = res.data.episodes;
|
||||
}
|
||||
closeDialog();
|
||||
dialogLoading.value = false;
|
||||
//episodes.value = res.data.episodes;
|
||||
openDialog(
|
||||
'info',
|
||||
'Attenzione',
|
||||
'L\'URL inserito non è un feed RSS valido'
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const edit = (item) => {
|
||||
podcastStore.loadPodcast(item);
|
||||
}
|
||||
|
||||
const cancel = (item) => {
|
||||
bulk.value = Array.isArray(item);
|
||||
itemEdited.value = item;
|
||||
openDialog(
|
||||
'delete',
|
||||
'Cancella',
|
||||
bulk.value ? 'Vuoi cancellare i podcast selezionati?' : 'Vuoi cancellare il podcast selezionato?'
|
||||
);
|
||||
}
|
||||
|
||||
const confirmDelete = (confirm, bulk) => {
|
||||
if (confirm) {
|
||||
if (!bulk) {
|
||||
deleteItem(itemEdited.value.id);
|
||||
} else {
|
||||
itemEdited.value.forEach(el => {
|
||||
deleteItem(el.id);
|
||||
})
|
||||
}
|
||||
}
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.open = false;
|
||||
itemEdited.value = basePodcast();
|
||||
}
|
||||
|
||||
const updateSearch = (text) => {
|
||||
search.value = text;
|
||||
}
|
||||
|
||||
const resetItemEdited = () => {
|
||||
podcastStore.currentPodcast = basePodcast();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Podcast</h1>
|
||||
<PodcastEditor
|
||||
v-if="podcastStore.currentPodcast.url != '' && podcastStore.currentPodcast.url != null"
|
||||
@go-back="resetItemEdited"
|
||||
/>
|
||||
<Table
|
||||
v-else
|
||||
:headers="headers"
|
||||
v-model:selected="selected"
|
||||
v-model:search="search"
|
||||
:list-data="listData"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
:get-items="getItems"
|
||||
:actions="true"
|
||||
:show-select="true"
|
||||
@update-table="getItems"
|
||||
@update-search="updateSearch"
|
||||
@delete-item="cancel"
|
||||
@edit-item="edit"
|
||||
>
|
||||
<template v-slot:header-buttons>
|
||||
<v-btn color="primary" @click="add">
|
||||
Aggiungi podcast
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:dialog>
|
||||
<v-dialog v-model="dialog.open">
|
||||
<ConfirmDelete
|
||||
:title="dialog.title"
|
||||
:text="dialog.text"
|
||||
:bulk="bulk"
|
||||
@confirm="confirm"
|
||||
@after-leave="closeDialog"
|
||||
:loading="dialogLoading"
|
||||
:hide_confirm="dialog.type === 'info' ? true : false"
|
||||
>
|
||||
<VTextField
|
||||
label="Feed RSS"
|
||||
v-if="dialog.type === 'add'"
|
||||
v-model="url"
|
||||
/>
|
||||
</ConfirmDelete>
|
||||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,13 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import {reactive, ref, watch} from "vue";
|
||||
import {onActivated, onBeforeMount, onMounted, type PropType, reactive, ref, watch} from "vue";
|
||||
import Table from "@partials/Table.vue";
|
||||
import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue";
|
||||
import {show_page} from "@/composables/content/show/show_page.ts";
|
||||
import ShowForm from "@partials/show/ShowForm.vue";
|
||||
import {baseShow, type Show} from "@models/show/show";
|
||||
import {useShowTypeStore} from "@stores/showType.store.ts";
|
||||
|
||||
const {items, listData, headers, selected, loading, search, getItems, editItem, deleteItem} = show_page()
|
||||
|
||||
const showTypeStore = useShowTypeStore();
|
||||
const showCreateEditMode = ref(false);
|
||||
let showSelected = ref<Number | null>(null);
|
||||
|
||||
|
@ -23,6 +25,7 @@ const dialog = reactive({
|
|||
text: ''
|
||||
})
|
||||
|
||||
// Funcs
|
||||
const openDialog = (type, title: string = '', text: string = '') => {
|
||||
dialog.open = true
|
||||
dialog.type = type
|
||||
|
@ -46,8 +49,8 @@ const saveItem = (item) => {
|
|||
}
|
||||
|
||||
const cancel = (item) => {
|
||||
let deleteMessage = 'Vuoi cancellare lo show selezionato?'
|
||||
if(bulk.value.state) deleteMessage = 'Vuoi cancellare gli show selezionati?'
|
||||
let deleteMessage = `Vuoi cancellare lo ${showTypeStore.currentType} selezionato?`
|
||||
if (bulk.value.state) deleteMessage = `Vuoi cancellare gli ${showTypeStore.currentType} selezionati?`
|
||||
bulk.value.items = item
|
||||
showSelected.value = item?.id
|
||||
openDialog(
|
||||
|
@ -78,19 +81,23 @@ const resetItemEdited = () => {
|
|||
showSelected.value = null
|
||||
}
|
||||
|
||||
watch(search, (newValue, oldValue) => {
|
||||
const options = {...listData};
|
||||
getItems(options)
|
||||
})
|
||||
|
||||
const goBack = () => {
|
||||
showCreateEditMode.value = false
|
||||
showSelected.value = null
|
||||
}
|
||||
|
||||
watch(search, (newValue, oldValue) => {
|
||||
const options = {...listData};
|
||||
getItems(options)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ShowForm v-if="showCreateEditMode" :showId="showSelected" @go-back="goBack"/>
|
||||
<div>
|
||||
<h1>
|
||||
{{ (showTypeStore.currentType === 'show') ? 'Trasmissioni' : 'Blocchi pubblicitari' }}
|
||||
</h1>
|
||||
<ShowForm v-if="showCreateEditMode" :showId="showSelected" :showType="showTypeStore.currentType" @go-back="goBack"/>
|
||||
<Table
|
||||
v-else
|
||||
:headers="headers"
|
||||
|
@ -109,7 +116,7 @@ const goBack = () => {
|
|||
>
|
||||
<template v-slot:header-buttons>
|
||||
<v-btn color="primary" @click="create">
|
||||
Crea una nuova trasmissione
|
||||
<span> {{ (showTypeStore.currentType === 'show') ? 'Crea una nuova trasmissione' : 'Crea un nuovo blocco pubblicitario' }} </span>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:dialog>
|
||||
|
@ -125,4 +132,5 @@ const goBack = () => {
|
|||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
|
@ -1,12 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import Table from "@/components/content/partials/Table.vue";
|
||||
import ConfirmDelete from "@/components/content/partials/dialogs/ConfirmDelete.vue";
|
||||
import {onBeforeMount, reactive, ref, watch} from "vue";
|
||||
import {onBeforeMount, type PropType, reactive, ref, watch} from "vue";
|
||||
import {smartblock_page} from "@/composables/content/smartblock_page.ts";
|
||||
import SmartBlockEditor from "@partials/SmartBlockEditor.vue";
|
||||
import {useSmartBlockStore} from "@/stores/smartblock.store.ts";
|
||||
import {baseSmartBlock} from "@models/smartblock/smartblock.ts";
|
||||
import {useShowTypeStore} from "@stores/showType.store.ts";
|
||||
|
||||
const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = smartblock_page()
|
||||
|
||||
// Props, data and stores
|
||||
const props = defineProps({
|
||||
hideColumns: {
|
||||
type: Array,
|
||||
|
@ -15,14 +19,9 @@ const props = defineProps({
|
|||
isDraggable: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const smartBlockStore = useSmartBlockStore()
|
||||
|
||||
const { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem } = smartblock_page()
|
||||
|
||||
const itemEdited = ref({
|
||||
id: null
|
||||
})
|
||||
|
@ -37,6 +36,11 @@ const dialog = reactive({
|
|||
|
||||
const visibleHeaders = ref(headers)
|
||||
|
||||
const showTypeStore = useShowTypeStore();
|
||||
const smartBlockStore = useSmartBlockStore()
|
||||
|
||||
|
||||
// Funcs
|
||||
const openDialog = (type, title = '', text = '') => {
|
||||
dialog.open = true
|
||||
dialog.type = type
|
||||
|
@ -49,6 +53,7 @@ const edit = (item) => {
|
|||
item = baseSmartBlock();
|
||||
}
|
||||
smartBlockStore.loadSmartBlock(item);
|
||||
smartBlockStore.currentSmartBlock.smart_block_type = showTypeStore.currentType;
|
||||
itemEdited.value = item;
|
||||
console.log(smartBlockStore)
|
||||
}
|
||||
|
@ -72,7 +77,7 @@ const cancel = (item) => {
|
|||
|
||||
const confirmDelete = (confirm) => {
|
||||
if (confirm) {
|
||||
if (!bulk) {
|
||||
if (!bulk.value) {
|
||||
deleteItem(itemEdited.value.id)
|
||||
} else {
|
||||
itemEdited.value.forEach(el => {
|
||||
|
@ -116,6 +121,8 @@ watch(search, (newValue, oldValue) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Blocchi dinamici {{ (showTypeStore.type === 'spot') ? 'pubblicitari' : '' }}</h1>
|
||||
<SmartBlockEditor
|
||||
v-if="itemEdited.id !== null && !dialog.open"
|
||||
:item="smartBlockStore.currentSmartBlock"
|
||||
|
@ -157,6 +164,7 @@ watch(search, (newValue, oldValue) => {
|
|||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
233
resources/js/components/content/UserProfile.vue
Normal file
233
resources/js/components/content/UserProfile.vue
Normal file
|
@ -0,0 +1,233 @@
|
|||
<script setup lang="ts">
|
||||
import {useAuthStore} from '@/stores/auth.store.ts';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {onBeforeMount, ref, reactive} from 'vue';
|
||||
import {useRouter} from "vue-router";
|
||||
import axios from "axios";
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const {userData} = storeToRefs(authStore);
|
||||
|
||||
const localUserData = reactive({...userData.value});
|
||||
|
||||
const emit = defineEmits([
|
||||
'userProfilePage'
|
||||
]);
|
||||
|
||||
let timezones = ref<string[]>([]);
|
||||
let roleList = ref<string[]>([]);
|
||||
|
||||
const form = ref<HTMLFormElement | null>(null);
|
||||
const passwordForm = ref<HTMLFormElement | null>(null);
|
||||
|
||||
const dialog = ref(false);
|
||||
const passwordData = reactive({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
||||
const formRules = {
|
||||
'emailRules': [(v: string) => !v || /.+@.+\..+/.test(v) || 'E-mail must be a valid format'],
|
||||
'cellphoneRules': [(v: string) => !v || /^[0-9-()]*$/.test(v) || 'Cellphone must be a valid number'],
|
||||
'passwordConfirmationRule': [
|
||||
(v: string) => !!v || 'Password confirmation is required',
|
||||
(v: string) => v === passwordData.newPassword || 'Passwords do not match'
|
||||
],
|
||||
'requiredRule': [(v: string) => !!v || 'This field is required'],
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
if (!form.value) return
|
||||
|
||||
const {valid} = await form.value.validate();
|
||||
if (!valid) return
|
||||
|
||||
authStore.userData = {...localUserData};
|
||||
await authStore.updateUser();
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
router.go(-1);
|
||||
}
|
||||
|
||||
const openPasswordDialog = () => {
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
const closePasswordDialog = () => {
|
||||
dialog.value = false;
|
||||
passwordForm.value?.reset();
|
||||
passwordForm.value?.resetValidation();
|
||||
};
|
||||
|
||||
const resetPassword = async () => {
|
||||
console.log('aaaa')
|
||||
try {
|
||||
await axios.put('/api/user/password', passwordData)
|
||||
console.log('Password changed');
|
||||
closePasswordDialog()
|
||||
return
|
||||
} catch (e) {
|
||||
const errorMessage = e.response?.data?.error || 'An unexpected error occurred.';
|
||||
console.error('Error changing password:' + errorMessage);
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await axios.get('/timezoneList').then(response => {
|
||||
timezones.value = response?.data
|
||||
})
|
||||
if (userData.value.role === 'admin') {
|
||||
await axios.get('/roleList').then(response => {
|
||||
roleList.value = response?.data
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-form ref="form">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.login"
|
||||
label="Login"
|
||||
required
|
||||
hint="Your public username."
|
||||
persistent-hint
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.email"
|
||||
:rules="formRules.emailRules"
|
||||
label="Email Address"
|
||||
hint="Used for notifications and account recovery."
|
||||
persistent-hint
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.firstName"
|
||||
label="First Name"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.lastName"
|
||||
label="Last Name"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="localUserData.cellPhone"
|
||||
:rules="formRules.cellphoneRules"
|
||||
label="Cell Phone"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-autocomplete
|
||||
v-model="localUserData.timezone"
|
||||
:items="timezones"
|
||||
label="Timezone"
|
||||
hint="Sets the time for all events and schedules."
|
||||
persistent-hint
|
||||
></v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="localUserData.role"
|
||||
:items="roleList"
|
||||
label="Ruolo"
|
||||
:disabled="userData.role !== 'admin'"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="d-flex justify-end">
|
||||
<v-btn color="secondary" @click="goBack" class="mr-4">
|
||||
Back
|
||||
</v-btn>
|
||||
<v-btn color="primary" @click="saveUser">
|
||||
Save Changes
|
||||
</v-btn>
|
||||
<v-btn color="error" @click="openPasswordDialog">
|
||||
Reset Password
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
<v-dialog v-model="dialog" persistent max-width="600px">
|
||||
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">Reset Your Password</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="text-subtitle-1 mb-4">
|
||||
Please be sure about the new password you are choosing. Password recovery via email is not implemented yet,
|
||||
so
|
||||
a forgotten password cannot be recovered.
|
||||
</p>
|
||||
</v-card-text>
|
||||
<v-form ref="passwordForm" @submit.prevent="resetPassword">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="passwordData.oldPassword"
|
||||
label="Old Password"
|
||||
type="password"
|
||||
:rules="formRules.requiredRule"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="passwordData.newPassword"
|
||||
label="New Password"
|
||||
type="password"
|
||||
:rules="formRules.requiredRule"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="passwordData.confirmPassword"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
:rules="formRules.passwordConfirmationRule"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="closePasswordDialog">
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn type="submit" color="blue-darken-1" variant="tonal">
|
||||
Confirm
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
153
resources/js/components/content/Webstream.vue
Normal file
153
resources/js/components/content/Webstream.vue
Normal file
|
@ -0,0 +1,153 @@
|
|||
<script setup lang="ts">
|
||||
import {onBeforeMount, reactive, ref, watch} from "vue";
|
||||
import Table from "@partials/Table.vue";
|
||||
import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue";
|
||||
import {webstream_page} from "@/composables/content/webstream_page.ts";
|
||||
import {baseWebstream, type Webstream} from "@models/webstream.ts";
|
||||
import WebstreamForm from "@partials/WebstreamForm.vue";
|
||||
|
||||
const {items, listData, headers, selected, loading, search, getItems, editItem, deleteItem} = webstream_page()
|
||||
|
||||
const props = defineProps({
|
||||
hideColumns: {
|
||||
type: Array,
|
||||
required: false
|
||||
},
|
||||
isDraggable: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const webstreamCreateEditMode = ref(false);
|
||||
let webstreamSelected = ref<Number | null>(null);
|
||||
|
||||
const visibleHeaders = ref(headers)
|
||||
|
||||
const bulk = ref({
|
||||
state: false,
|
||||
items: [] as Webstream[],
|
||||
})
|
||||
|
||||
const dialog = reactive({
|
||||
open: false,
|
||||
type: '',
|
||||
title: '',
|
||||
text: ''
|
||||
})
|
||||
|
||||
const openDialog = (type, title: string = '', text: string = '') => {
|
||||
dialog.open = true
|
||||
dialog.type = type
|
||||
dialog.title = title
|
||||
dialog.text = text
|
||||
}
|
||||
|
||||
const edit = (webstreamSelectedFromUser) => {
|
||||
webstreamSelected.value = webstreamSelectedFromUser.id
|
||||
webstreamCreateEditMode.value = true
|
||||
}
|
||||
|
||||
const create = () => {
|
||||
webstreamSelected.value = null
|
||||
webstreamCreateEditMode.value = true
|
||||
}
|
||||
|
||||
const saveItem = (item) => {
|
||||
const saved = editItem(item)
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
const cancel = (item) => {
|
||||
let deleteMessage = 'Vuoi cancellare il webstream selezionato?'
|
||||
if(bulk.value.state) deleteMessage = 'Vuoi cancellare i webstream selezionati?'
|
||||
bulk.value.items = item
|
||||
webstreamSelected.value = item?.id
|
||||
openDialog(
|
||||
'delete',
|
||||
'Cancella',
|
||||
deleteMessage
|
||||
)
|
||||
}
|
||||
|
||||
const confirmDelete = (confirm) => {
|
||||
if (confirm) {
|
||||
const webstreamId = webstreamSelected.value == 0 ? null : webstreamSelected.value ;
|
||||
deleteItem(webstreamId)
|
||||
}
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.open = false
|
||||
resetItemEdited()
|
||||
}
|
||||
|
||||
const updateSearch = (text) => {
|
||||
search.value = text
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if(props.hideColumns != undefined) {
|
||||
visibleHeaders.value = headers.filter(el => {
|
||||
return !props.hideColumns.includes(el.value)
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
const resetItemEdited = () => {
|
||||
webstreamSelected.value = null
|
||||
}
|
||||
|
||||
watch(search, (newValue, oldValue) => {
|
||||
const options = {...listData};
|
||||
getItems(options)
|
||||
})
|
||||
|
||||
const goBack = () => {
|
||||
webstreamCreateEditMode.value = false
|
||||
webstreamSelected.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Webstream</h1>
|
||||
<WebstreamForm v-if="webstreamCreateEditMode" :webstreamId="webstreamSelected" @go-back="goBack"/>
|
||||
<Table
|
||||
v-else
|
||||
:headers="visibleHeaders"
|
||||
v-model:selected="selected"
|
||||
v-model:search="search"
|
||||
:list-data="listData"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
:get-items="getItems"
|
||||
:actions="true"
|
||||
:show-select="true"
|
||||
:is-draggable="isDraggable"
|
||||
@update-table="getItems"
|
||||
@update-search="updateSearch"
|
||||
@delete-item="cancel"
|
||||
@edit-item="edit"
|
||||
>
|
||||
<template v-slot:header-buttons>
|
||||
<v-btn color="primary" @click="create">
|
||||
Crea un nuovo webstream
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:dialog>
|
||||
<v-dialog v-model="dialog.open">
|
||||
<ConfirmDelete
|
||||
v-if="dialog.type === 'delete'"
|
||||
:title="dialog.title"
|
||||
:text="dialog.text"
|
||||
:bulk="bulk.state"
|
||||
@confirm="confirmDelete"
|
||||
@after-leave="closeDialog"
|
||||
/>
|
||||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
216
resources/js/components/content/partials/PodcastEditor.vue
Normal file
216
resources/js/components/content/partials/PodcastEditor.vue
Normal file
|
@ -0,0 +1,216 @@
|
|||
<script setup lang="ts">
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
import {usePodcastStore} from "@/stores/podcast.store.ts";
|
||||
import {podcast} from "@models/podcast/podcast.ts";
|
||||
import {podcast_episode_page} from "@/composables/content/podcastEpisode_page.ts";
|
||||
import {onBeforeMount, reactive, ref, watch} from "vue";
|
||||
import axios from "axios";
|
||||
import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue";
|
||||
import {podcast_page} from "@/composables/content/podcast_page.ts";
|
||||
|
||||
const auth = useAuthStore();
|
||||
|
||||
const emit = defineEmits([
|
||||
'saveItem',
|
||||
'goBack'
|
||||
])
|
||||
|
||||
const podcastStore = usePodcastStore();
|
||||
const item = podcastStore.currentPodcast;
|
||||
|
||||
const { items, headers, loading, downloadEpisode, getItems } = podcast_episode_page(item.url);
|
||||
|
||||
const podcast_id = ref(item.id);
|
||||
console.log(item)
|
||||
|
||||
const podcastFields = podcast(item);
|
||||
console.log(podcastFields())
|
||||
const { editItem } = podcast_page();
|
||||
|
||||
const episodes = ref([]);
|
||||
|
||||
const disabledSaveButton = ref(false)
|
||||
|
||||
const dialog = reactive({
|
||||
open: false,
|
||||
type: '',
|
||||
title: '',
|
||||
text: '',
|
||||
item: null
|
||||
})
|
||||
|
||||
const openDialog = (type, title = '', text = '', item = null) => {
|
||||
dialog.open = true
|
||||
dialog.type = type
|
||||
dialog.title = title
|
||||
dialog.text = text
|
||||
dialog.item = item
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.open = false
|
||||
}
|
||||
|
||||
const checkError = (field, model) => {
|
||||
if (field.required) {
|
||||
const error = field.required && (model === '' || model === null)
|
||||
disabledSaveButton.value = error
|
||||
return error
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const checkDownload = (item) => {
|
||||
if (podcast_id.value > 0) {
|
||||
// console.log(item);
|
||||
downloadEpisode(podcast_id.value, item);
|
||||
} else {
|
||||
openDialog(
|
||||
'save',
|
||||
'Salvataggio necessario',
|
||||
'Per procedere con il download dell\'episodio è necessario salvare il podcast. Confermi?',
|
||||
item
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmSave = (confirm, bulk, row) => {
|
||||
console.log(confirm, row)
|
||||
if (confirm) {
|
||||
save(row);
|
||||
}
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
const save = (row) => {
|
||||
console.log(row)
|
||||
// const errors
|
||||
editItem(item).then(res => {
|
||||
podcastStore.loadPodcast(res.podcast);
|
||||
podcast_id.value = res.podcast.id;
|
||||
console.log(podcast_id.value);
|
||||
//Check if row is effectively a podcast episode object using his `title` property
|
||||
if (row.title) {
|
||||
downloadEpisode(podcast_id.value, row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watch(items, (newItems, oldItems) => {
|
||||
episodes.value = newItems;
|
||||
}, {deep: true});
|
||||
|
||||
setInterval(() => {
|
||||
getItems(false, item.id);
|
||||
}, 5000)
|
||||
|
||||
onBeforeMount(() => {
|
||||
getItems().then(title => podcastStore.updateField({'title': title}));
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row no-gutters>
|
||||
<v-col>
|
||||
<Component
|
||||
v-for="(field, key) in podcastFields()"
|
||||
:is="field.component"
|
||||
:label="field.label"
|
||||
:value="field.value ? field.value : field.type == 'checkbox' ? true : null"
|
||||
:disabled="field.disabled"
|
||||
@update:modelValue="checkError(field, item[key])"
|
||||
:error="checkError(field, item[key])"
|
||||
rows="2"
|
||||
:items="field.items"
|
||||
v-model="item[key]"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
class="mb-2"
|
||||
clearable
|
||||
:active="true"
|
||||
/>
|
||||
<v-btn
|
||||
color="accent"
|
||||
@click="$emit('goBack')"
|
||||
>Torna indietro</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="save"
|
||||
:disabled="disabledSaveButton"
|
||||
>Salva</v-btn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<!-- Tracks-->
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="episodes"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-slot:item.short_description="{ item }">
|
||||
{{ item.short_description }}
|
||||
</template>
|
||||
<template v-slot:item.imported="{ item }">
|
||||
<v-icon
|
||||
class="me-2 spinning"
|
||||
size="small"
|
||||
v-if="item.imported === 'PENDING'"
|
||||
>
|
||||
mdi-loading
|
||||
</v-icon>
|
||||
<v-icon
|
||||
class="me-2"
|
||||
size="small"
|
||||
v-else-if="item.imported === 'SUCCESS'"
|
||||
>
|
||||
mdi-check-outline
|
||||
</v-icon>
|
||||
<v-icon
|
||||
class="me-2 text-center"
|
||||
size="small"
|
||||
v-else-if="item.imported === 0"
|
||||
@click="checkDownload(item)"
|
||||
>
|
||||
mdi-download-box
|
||||
</v-icon>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-dialog v-model="dialog.open">
|
||||
<ConfirmDelete
|
||||
v-if="dialog.type === 'save'"
|
||||
:title="dialog.title"
|
||||
:text="dialog.text"
|
||||
:bulk="false"
|
||||
:item="dialog.item"
|
||||
@confirm="confirmSave"
|
||||
@after-leave="closeDialog"
|
||||
/>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tables > .v-col {
|
||||
width: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.v-list {
|
||||
max-height: 77vh;
|
||||
margin: 4px 0 0 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spinning {
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -6,26 +6,24 @@ import {smartblock} from "@models/smartblock/smartblock.ts";
|
|||
import {smartblock_page} from "@/composables/content/smartblock_page.ts";
|
||||
import {formatFromSeconds} from "@/helpers/TimeFormatter.ts";
|
||||
import {useSmartBlockStore} from "@/stores/smartblock.store.ts";
|
||||
|
||||
const auth = useAuthStore();
|
||||
import {baseSmartBlockCriteria} from "@models/smartblock/smartblockCriteria.ts";
|
||||
|
||||
const { loading, getTracklist } = smartblock_page()
|
||||
|
||||
const emit = defineEmits([
|
||||
'saveItem'
|
||||
])
|
||||
|
||||
//
|
||||
const auth = useAuthStore();
|
||||
const smartBlockStore = useSmartBlockStore();
|
||||
const item = smartBlockStore.currentSmartBlock;
|
||||
console.log(smartBlockStore.currentSmartBlock)
|
||||
|
||||
const length = ref([]);
|
||||
|
||||
const smartblockFields = smartblock(item)
|
||||
const length = ref([]);
|
||||
|
||||
//Is true if there is a required field empty or while saving
|
||||
const disabledSaveButton = ref(true)
|
||||
|
||||
// Funcs
|
||||
const update = (list) => {
|
||||
item.tracks = list
|
||||
}
|
||||
|
@ -61,8 +59,25 @@ const showPreview = async () => {
|
|||
})
|
||||
}
|
||||
|
||||
function hiddenSmartBlockCriteria(){
|
||||
if(!smartBlockStore.currentSmartBlock.smart_block_type) return
|
||||
|
||||
let showSmartBlockCriteria = baseSmartBlockCriteria()
|
||||
showSmartBlockCriteria.criteria = 'track_type_id'
|
||||
showSmartBlockCriteria.modifier = 'is not'
|
||||
showSmartBlockCriteria.value = '3'
|
||||
|
||||
if (smartBlockStore.currentSmartBlock.smart_block_type == 'spot') {
|
||||
showSmartBlockCriteria.modifier = 'is'
|
||||
}
|
||||
smartBlockStore.currentSmartBlock.criteria.push(showSmartBlockCriteria)
|
||||
}
|
||||
|
||||
|
||||
// Hook and watch
|
||||
onMounted(() => {
|
||||
smartBlockStore.updateField({key: 'creator_id', value: auth.userData.user.id})
|
||||
smartBlockStore.updateField({key: 'creator_id', value: auth.userData.id})
|
||||
hiddenSmartBlockCriteria()
|
||||
})
|
||||
|
||||
watch(loading.value, (newVal, oldVal) => {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import {ref} from "vue";
|
||||
import {onBeforeMount, ref} from "vue";
|
||||
import Archive from "@/components/content/Archive.vue";
|
||||
import Blocks from "@components/content/SmartBlock.vue";
|
||||
import Webstream from "@components/content/Webstream.vue";
|
||||
import {useShowTypeStore} from "@stores/showType.store.ts";
|
||||
|
||||
const tab = ref(null)
|
||||
const tabs = [
|
||||
|
@ -13,7 +15,20 @@ const tabs = [
|
|||
id: 'blocks',
|
||||
title: 'Blocchi dinamici',
|
||||
},
|
||||
{
|
||||
id: 'webstream',
|
||||
title: 'Webstream',
|
||||
},
|
||||
]
|
||||
|
||||
onBeforeMount(() => {
|
||||
const showTypeStore = useShowTypeStore()
|
||||
if(showTypeStore.currentType == 'spot') {
|
||||
const webstreamIndex = tabs.findIndex(tab => tab.id == 'webstream')
|
||||
tabs.splice(webstreamIndex, 1)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -42,6 +57,11 @@ const tabs = [
|
|||
:is-draggable="true"
|
||||
:hideColumns="['mtime', 'utime', 'actions']"
|
||||
/>
|
||||
<Webstream
|
||||
v-if="tab.id === 'webstream'"
|
||||
:show-select="false"
|
||||
:is-draggable="true"
|
||||
/>
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
</template>
|
||||
|
|
|
@ -65,12 +65,30 @@ const rehydratateTracks = () => {
|
|||
track_info.block = track
|
||||
track_info.id = track.id
|
||||
}
|
||||
if (Object.hasOwn(track, 'webstream') && track.webstream !== null) {
|
||||
track_info.type = 'webstream'
|
||||
track_info.title = track.webstream.name
|
||||
track_info.subtitle = track.webstream.description
|
||||
track_info.db_element = track.webstream
|
||||
track_info.webstream = track.webstream
|
||||
track_info.id = track.stream_id
|
||||
}
|
||||
if (!Object.hasOwn(track, 'webstream') && track.type === 'webstream') {
|
||||
track_info.type = 'webstream'
|
||||
track_info.title = track.name
|
||||
track_info.subtitle = track.description
|
||||
track_info.db_element = track
|
||||
track_info.webstream = track
|
||||
track_info.id = track.id
|
||||
}
|
||||
return track_info
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
rehydratateTracks()
|
||||
if (typeof props.tracks === "object") {
|
||||
rehydratateTracks();
|
||||
}
|
||||
})
|
||||
|
||||
const checkMove = (e) => {
|
||||
|
@ -100,6 +118,15 @@ const change = (event) => {
|
|||
trackList.value[event.added.newIndex].title = trackList.value[event.added.newIndex].name;
|
||||
trackList.value[event.added.newIndex].subtitle = trackList.value[event.added.newIndex].creator.login;
|
||||
}
|
||||
// webstream
|
||||
if (
|
||||
Object.hasOwn(trackList.value[event.added.newIndex], 'url') &&
|
||||
!Object.hasOwn(trackList.value[event.added.newIndex], 'track_title')
|
||||
) {
|
||||
trackList.value[event.added.newIndex].type = 'webstream';
|
||||
trackList.value[event.added.newIndex].title = trackList.value[event.added.newIndex].name;
|
||||
trackList.value[event.added.newIndex].subtitle = trackList.value[event.added.newIndex].description;
|
||||
}
|
||||
}
|
||||
emit('updateTracks', trackList.value)
|
||||
}
|
||||
|
|
141
resources/js/components/content/partials/WebstreamForm.vue
Normal file
141
resources/js/components/content/partials/WebstreamForm.vue
Normal file
|
@ -0,0 +1,141 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, onMounted, type PropType, reactive} from "vue";
|
||||
import {baseWebstream, createWebstream, getWebstream, updateWebstream, type Webstream} from "@models/webstream.ts";
|
||||
|
||||
// Props and emits
|
||||
const props = defineProps({
|
||||
webstreamId: {
|
||||
type: Number as PropType<number | null>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['go-back']);
|
||||
|
||||
// Data
|
||||
const loading = ref(false);
|
||||
const isFormValid = ref(false);
|
||||
const currentWebstream = ref<Webstream>(baseWebstream());
|
||||
|
||||
// Funcs
|
||||
onMounted(async () => {
|
||||
loading.value = true;
|
||||
if (props.webstreamId === null) {
|
||||
currentWebstream.value = baseWebstream();
|
||||
} else {
|
||||
currentWebstream.value = await getWebstream(props.webstreamId);
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
const goBack = () => {
|
||||
emits('go-back');
|
||||
};
|
||||
|
||||
const saveWebstream = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (currentWebstream.value.id) {
|
||||
await updateWebstream(currentWebstream.value);
|
||||
goBack();
|
||||
return
|
||||
}
|
||||
|
||||
await createWebstream(currentWebstream.value);
|
||||
goBack()
|
||||
return
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error saving webstream:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-slot:loader="{ isActive }">
|
||||
<v-progress-linear
|
||||
:active="isActive"
|
||||
color="deep-purple"
|
||||
height="4"
|
||||
indeterminate
|
||||
></v-progress-linear>
|
||||
</template>
|
||||
|
||||
<v-card-title>
|
||||
<h3>Webstream</h3>
|
||||
</v-card-title>
|
||||
<v-form ref="form" v-model="isFormValid">
|
||||
<v-card-text>
|
||||
<v-row no-gutters>
|
||||
<!-- Name Field -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-card>
|
||||
<v-text-field
|
||||
v-model="currentWebstream.name"
|
||||
label="Nome"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'Nome è obbligatorio']"
|
||||
required="true"
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- URL Field -->
|
||||
<v-col cols="12" md="6">7
|
||||
<v-card>
|
||||
<v-text-field
|
||||
v-model="currentWebstream.url"
|
||||
label="URL"
|
||||
density="compact"
|
||||
:rules="[v => !!v || 'URL è obbligatorio', v => /^http?:\/\//.test(v) || 'URL deve iniziare con http://']"
|
||||
required="true"
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Description Field -->
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-textarea
|
||||
v-model="currentWebstream.description"
|
||||
label="Descrizione"
|
||||
density="compact"
|
||||
rows="2"
|
||||
:rules="[v => !!v || 'Descrizione è obbligatoria']"
|
||||
required="true"
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Length Field -->
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-textarea
|
||||
v-model="currentWebstream.length"
|
||||
label="Durata"
|
||||
density="compact"
|
||||
rows="2"
|
||||
:rules="[v => !!v || 'Durata è obbligatoria']"
|
||||
required="true"
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn color="accent" @click="goBack">Torna indietro</v-btn>
|
||||
<v-btn color="primary" @click="saveWebstream" :disabled="!isFormValid">
|
||||
{{ currentWebstream.id ? 'Aggiorna' : 'Crea' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -2,10 +2,30 @@
|
|||
const props = defineProps({
|
||||
title: String,
|
||||
text: String,
|
||||
bulk: Boolean
|
||||
confirm_text: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
cancel_text: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
bulk: Boolean,
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
hide_confirm: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
console.log(props.bulk)
|
||||
console.log(props.loading)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -15,16 +35,19 @@ console.log(props.bulk)
|
|||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p>{{ props.text }}</p>
|
||||
<slot></slot>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="$emit('confirm',true, props.bulk)"
|
||||
@click="$emit('confirm',true, props.bulk, item)"
|
||||
:loading="loading"
|
||||
v-if="hide_confirm != true"
|
||||
>Conferma</v-btn>
|
||||
<v-btn
|
||||
color="accent"
|
||||
@click="$emit('confirm',false)"
|
||||
>Cancella</v-btn>
|
||||
>Annulla</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
|
|
@ -32,6 +32,7 @@ const uploadFiles = async () => {
|
|||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileToUpload.file);
|
||||
formData.append('track_type_id', selectedTrackType.value.id);
|
||||
const response = await uploadItem(formData);
|
||||
|
||||
fileToUpload.id = response?.id;
|
||||
|
@ -41,9 +42,8 @@ const uploadFiles = async () => {
|
|||
);
|
||||
});
|
||||
|
||||
await Promise.all(uploadPromises);
|
||||
|
||||
uploadingFilesNow.value = false;
|
||||
await Promise.all(uploadPromises)
|
||||
//uploadingFilesNow.value = false;
|
||||
};
|
||||
|
||||
const checkUploadStatus = async (file) => {
|
||||
|
@ -54,8 +54,7 @@ const checkUploadStatus = async (file) => {
|
|||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
if (response.data.import_status === 1) {
|
||||
//checkUploadStatus(id)
|
||||
console.log('test')
|
||||
|
||||
} else {
|
||||
file.uploadingNow = false
|
||||
clearInterval(file.checkAnalyzer);
|
||||
|
@ -64,6 +63,13 @@ const checkUploadStatus = async (file) => {
|
|||
})
|
||||
}
|
||||
|
||||
watch(selectedFilesMetadata, (newValue, oldValue) => {
|
||||
const notUploading = newValue.every(data => data.uploadingNow)
|
||||
console.log(newValue)
|
||||
if (!notUploading) {
|
||||
uploadingFilesNow.value = false;
|
||||
}
|
||||
}, {deep: true})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -107,18 +113,25 @@ const checkUploadStatus = async (file) => {
|
|||
{{ track.file.name }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
{{ selectedTrackType }}
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
color="primary"
|
||||
size="large"
|
||||
variant="flat"
|
||||
type="submit"
|
||||
variant="elevated"
|
||||
:disabled="selectedFiles.length === 0 || uploadingFilesNow"
|
||||
:loading="uploadingFilesNow"
|
||||
:disabled="selectedFiles.length === 0"
|
||||
@click="uploadFiles"
|
||||
>Carica
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
size="large"
|
||||
variant="flat"
|
||||
:disabled="uploadingFilesNow"
|
||||
@click="$emit('confirm',false)"
|
||||
>Chiudi</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, onMounted, type PropType} from "vue";;
|
||||
import {ref, onMounted, type PropType} from "vue";
|
||||
|
||||
;
|
||||
import ShowScheduleForm from "@partials/show/ShowScheduleForm.vue";
|
||||
import {useShowStore} from "@/stores/show.store.ts";
|
||||
import {useShowStore} from "@stores/show/show.store.ts";
|
||||
import {getUser} from "@models/User.ts";
|
||||
import {getPlaylist} from "@models/playlist.ts";
|
||||
import ColorPickerButton from "@partials/fields/misc/ColorPickerButton.vue";
|
||||
import {useShowDaysStore} from "@/stores/showDays.store.ts";
|
||||
import {useShowDaysStore} from "@stores/show/showDays.store.ts";
|
||||
|
||||
// Props and emits
|
||||
const props = defineProps({
|
||||
|
@ -13,6 +15,11 @@ const props = defineProps({
|
|||
type: Number as PropType<number | null>,
|
||||
required: true,
|
||||
},
|
||||
showType: {
|
||||
type: String as PropType<'show' | 'spot'>,
|
||||
required: true,
|
||||
validator: (value: string) => ['show', 'spot'].includes(value),
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['go-back']);
|
||||
|
||||
|
@ -31,15 +38,27 @@ const showDaysStore = useShowDaysStore()
|
|||
// Funcs
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
// Prepare show store
|
||||
if (props.showId === null) {
|
||||
showStore.resetShow()
|
||||
showStore.currentShow.showType = props.showType
|
||||
} else {
|
||||
const selectedShow = await showStore.getShow(props.showId, {withDjs: true})
|
||||
const withDjs = props.showType === 'show';
|
||||
const selectedShow = await showStore.getShow(props.showId, {showType: props.showType, withDjs: withDjs})
|
||||
showStore.loadShow(selectedShow)
|
||||
showStore.currentShow.showType = props.showType
|
||||
}
|
||||
|
||||
// fill store
|
||||
let playlistOptions: { playlistType: 'show' | 'spot' } = { playlistType: 'spot' };
|
||||
|
||||
if (props.showType === 'show') {
|
||||
usersDJs.value = await getUser({role: 'dj'});
|
||||
playlists.value = await getPlaylist({});
|
||||
loading.value = false
|
||||
playlistOptions.playlistType = 'show';
|
||||
}
|
||||
|
||||
playlists.value = await getPlaylist(playlistOptions);
|
||||
loading.value = false;
|
||||
})
|
||||
|
||||
const toggleShowScheduleForm = () => {
|
||||
|
@ -105,7 +124,7 @@ const createShow = () => {
|
|||
</v-col>
|
||||
|
||||
<!-- URL Field -->
|
||||
<v-col cols="12" md="6" lg="4">
|
||||
<v-col v-if="props.showType == 'show'" cols="12" md="6" lg="4">
|
||||
<v-card>
|
||||
<v-text-field
|
||||
v-model="showStore.currentShow.url"
|
||||
|
@ -197,7 +216,7 @@ const createShow = () => {
|
|||
|
||||
<!-- TODO Instead of the dj name, obj obj is shown -->
|
||||
<!-- DJs Select -->
|
||||
<v-col cols="12" md="6" lg="4">
|
||||
<v-col v-if="props.showType == 'show'" cols="12" md="6" lg="4">
|
||||
<v-card>
|
||||
<v-select
|
||||
v-model="showStore.currentShow.showDjs"
|
||||
|
@ -216,8 +235,11 @@ const createShow = () => {
|
|||
|
||||
<v-card-actions>
|
||||
<v-btn color="accent" @click="goBack">Torna indietro</v-btn>
|
||||
<v-btn v-if="showStore.currentShow.id" color="accent" @click="showStore.updateShow()" :disabled="!isFormValid" >Salva</v-btn>
|
||||
<v-btn color="accent" @click="toggleShowScheduleForm" :disabled="!isFormValid" >Regole di programmazione</v-btn>
|
||||
<v-btn v-if="showStore.currentShow.id" color="accent" @click="showStore.updateShow()"
|
||||
:disabled="!isFormValid">Salva
|
||||
</v-btn>
|
||||
<v-btn color="accent" @click="toggleShowScheduleForm" :disabled="!isFormValid">Regole di programmazione
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-form>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import ShowStartEndTime from "@partials/fields/show/ShowStartEndTime.vue";
|
||||
import {useShowInstanceStore} from "@/stores/showInstance.store.ts";
|
||||
import {useShowInstanceStore} from "@stores/show/showInstance.store.ts";
|
||||
import {onMounted, ref, watch, type PropType} from "vue";
|
||||
import {baseShowInstance, type ShowInstance} from "@models/show/showInstance.ts";
|
||||
import {extractTime} from "@/helpers/DateFormatter.ts";
|
||||
|
@ -8,7 +8,7 @@ import {getPlaylistContent} from "@models/playlist.ts";
|
|||
import Sources from "@partials/Sources.vue";
|
||||
import TrackList from "@partials/TrackList.vue";
|
||||
import {DateTime} from "luxon";
|
||||
import {useShowStore} from "@/stores/show.store.ts";
|
||||
import {useShowStore} from "@stores/show/show.store.ts";
|
||||
|
||||
const emits = defineEmits(['toggle-menu-edit-instance']);
|
||||
// Props
|
||||
|
|
|
@ -3,7 +3,7 @@ import {onMounted, type PropType, ref, watch} from 'vue';
|
|||
import {showRepetitionData} from "@models/show/ShowRepetition.ts";
|
||||
import DaysCheckbox from "@partials/fields/show/DaysCheckbox.vue";
|
||||
import ShowStartEndTime from "@partials/fields/show/ShowStartEndTime.vue";
|
||||
import {useShowDaysStore} from "@/stores/showDays.store.ts";
|
||||
import {useShowDaysStore} from "@stores/show/showDays.store.ts";
|
||||
|
||||
// Emits and props
|
||||
const emits = defineEmits(['toggle-show-schedule-form', 'trigger-show-creation'])
|
||||
|
|
|
@ -11,19 +11,17 @@ const color = ref("secondary");
|
|||
|
||||
const isOnAir = async (): void => {
|
||||
const now = DateTime.now().setZone(import.meta.env.VITE_APP_TIMEZONE).toISO();
|
||||
return await axios.get(`/api/v2/schedule`, {
|
||||
auth: {
|
||||
username: auth.userData.user.login,
|
||||
password: auth.userData.password
|
||||
},
|
||||
return await axios.get(`/schedule`, {
|
||||
params: {
|
||||
ends_after: now,
|
||||
starts_before: now,
|
||||
ends: now,
|
||||
start: now,
|
||||
}
|
||||
}).then((response: AxiosResponse) => {
|
||||
if (typeof response.data === Array && response.data.length > 0) {
|
||||
color.value = 'error';
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import {useRouter} from "vue-router";
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
import axios from "axios";
|
||||
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
const userInfo = auth.userData;
|
||||
if (!userInfo) {
|
||||
router.push('/login');
|
||||
}
|
||||
const userName = auth.userData.login;
|
||||
|
||||
const logout = () => {
|
||||
auth.logout();
|
||||
router.push('/login');
|
||||
}
|
||||
const logout = async () => {
|
||||
await auth.logout()
|
||||
router.push({ path: 'login' });
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -20,16 +18,17 @@ const logout = () => {
|
|||
<v-sheet
|
||||
:width="150"
|
||||
>
|
||||
<v-btn color="info">{{ userName }} {{ $t('header.userinfo.info') }}</v-btn>
|
||||
<v-btn color="" @click="logout">{{ $t('header.userinfo.logout') }}</v-btn>
|
||||
<v-btn color="secondary" :to="{ path: 'user-profile'}">{{ userName }} {{ $t('header.userinfo.info') }}</v-btn>
|
||||
<v-btn color="error" @click="logout">{{ $t('header.userinfo.logout') }}</v-btn>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
div button {
|
||||
div button, div a {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div button:not(:first-child) {
|
||||
margin-top: 2px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,6 @@
|
|||
import axios from "axios";
|
||||
import {ref, reactive, computed} from "vue";
|
||||
import {useShowTypeStore} from "@stores/showType.store.ts";
|
||||
|
||||
export function archive_page() {
|
||||
const items = ref([])
|
||||
|
@ -13,6 +14,7 @@ export function archive_page() {
|
|||
'total_items': 0,
|
||||
'page': 1,
|
||||
})
|
||||
const showTypeStore = useShowTypeStore();
|
||||
|
||||
const headers = [
|
||||
// {title: '', key: 'artwork'},
|
||||
|
@ -30,12 +32,14 @@ export function archive_page() {
|
|||
*/
|
||||
const getItems = async (page_info) => {
|
||||
loading.value = true;
|
||||
return await axios.get(`/file`, {
|
||||
params: {
|
||||
let options = {
|
||||
page: page_info.page,
|
||||
per_page: page_info.itemsPerPage,
|
||||
all: search.value
|
||||
}
|
||||
options['track_type'] = showTypeStore.currentType
|
||||
return await axios.get(`/file`, {
|
||||
params: options
|
||||
}).then((response) => {
|
||||
//console.log(response)
|
||||
listData.itemsPerPage = response.data.per_page;
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface User {
|
|||
lastName: string;
|
||||
email?: boolean;
|
||||
cellPhone?: boolean;
|
||||
timezone?: string;
|
||||
|
||||
show?: Show[];
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ export function playlist(item) {
|
|||
}
|
||||
|
||||
// TODO playlist interface
|
||||
// TODO Add filter if playlist is spot
|
||||
export const getPlaylist = async (options: {
|
||||
id?: number | null;
|
||||
scheduled?: number | null;
|
||||
|
@ -48,6 +49,7 @@ export const getPlaylist = async (options: {
|
|||
page?: Number | null;
|
||||
per_page?: Number | null;
|
||||
all?: string | null;
|
||||
playlistType?: 'show' | 'spot' | null;
|
||||
}): Promise<any> => {
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.get(`/playlist`, {params: filteredParams})
|
||||
|
|
|
@ -11,6 +11,7 @@ export interface Playlist {
|
|||
description?: string; // Opzionale
|
||||
length: number; // Durata della playlist
|
||||
contents: PlaylistContent[];
|
||||
playlist_type: 'show' | 'spot' | null
|
||||
}
|
||||
|
||||
export const basePlaylist = (): Playlist => {
|
||||
|
@ -21,6 +22,7 @@ export const basePlaylist = (): Playlist => {
|
|||
description: '',
|
||||
length: 0,
|
||||
contents: [],
|
||||
playlist_type: null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
109
resources/js/composables/content/models/podcast/podcast.ts
Normal file
109
resources/js/composables/content/models/podcast/podcast.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Interfaccia Podcast
|
||||
import {cleanOptions} from "@/helpers/AxiosHelper.ts";
|
||||
import axios, {type AxiosResponse} from "axios";
|
||||
import {VCheckbox, VTextField} from "vuetify/components";
|
||||
import type {PodcastEpisode} from "@models/podcast/podcastEpisode.ts";
|
||||
|
||||
export interface Podcast {
|
||||
id: number;
|
||||
url: string;
|
||||
title: string;
|
||||
creator?: string;
|
||||
description: string;
|
||||
language: string;
|
||||
copyright?: string;
|
||||
link: string;
|
||||
itunes_author?: string;
|
||||
itunes_keywords?: string;
|
||||
itunes_summary?: string;
|
||||
itunes_subtitle?: string;
|
||||
itunes_category?: string;
|
||||
itunes_explicit?: string;
|
||||
owner: number; // ID dell'owner
|
||||
episodes?: PodcastEpisode[];
|
||||
}
|
||||
|
||||
// Costante basePodcast
|
||||
export const basePodcast = (): Podcast => {
|
||||
return {
|
||||
id: 0,
|
||||
url: '',
|
||||
title: '',
|
||||
description: '',
|
||||
language: '',
|
||||
link: '',
|
||||
itunes_explicit: 'false',
|
||||
owner: 0,
|
||||
}
|
||||
};
|
||||
|
||||
export const PodcastTableHeader = [
|
||||
{title: 'Nome', value: 'title'},
|
||||
{title: 'Creato da', value: 'owner.login'},
|
||||
{title: 'Data di importazione', value: 'imported.auto_ingest_timestamp'},
|
||||
{title: 'Azioni', value: 'actions'}
|
||||
];
|
||||
|
||||
export const getPodcast = async (options: {
|
||||
id?: number | null;
|
||||
page?: Number | null;
|
||||
per_page?: Number | null;
|
||||
all?: string | null;
|
||||
}): Promise<Podcast[]> => {
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.get(`/podcast`, {params: filteredParams})
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
})
|
||||
}
|
||||
|
||||
export const deletePodcast = async (podcastIds: Number[]) => {
|
||||
return axios.delete(`podcast`, {
|
||||
data: {
|
||||
_method: 'DELETE',
|
||||
'podcastIds': podcastIds
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function podcast(item) {
|
||||
const visibleFields = {
|
||||
title: {
|
||||
title: 'Nome del podcast',
|
||||
required: true,
|
||||
disabled: false
|
||||
},
|
||||
url: {
|
||||
title: 'URL del podcast',
|
||||
required: true,
|
||||
disabled: true
|
||||
},
|
||||
auto_ingest: {
|
||||
title: 'Scarica l\'ultimo episodio in automatico',
|
||||
required: false,
|
||||
disabled: false
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const fields = {}
|
||||
Object.keys(visibleFields).forEach((key) => {
|
||||
fields[key] = {
|
||||
label: visibleFields[key].title,
|
||||
value: item !== null ? item[key] : '',
|
||||
required: visibleFields[key].required,
|
||||
disabled: (visibleFields[key].disabled !== undefined) ? visibleFields[key].disabled : false,
|
||||
component: VTextField
|
||||
}
|
||||
// console.log(fields)
|
||||
switch (key) {
|
||||
case 'auto_ingest':
|
||||
fields[key].component = VCheckbox
|
||||
break
|
||||
}
|
||||
})
|
||||
return fields
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import type {Podcast} from "@models/podcast/podcast.ts";
|
||||
import {cleanOptions} from "@/helpers/AxiosHelper.ts";
|
||||
import axios, {type AxiosResponse} from "axios";
|
||||
|
||||
export interface PodcastEpisode {
|
||||
id: number;
|
||||
file_id?: number;
|
||||
podcast_id: number;
|
||||
publication_date: Date;
|
||||
download_url: string;
|
||||
episode_guid: string;
|
||||
episode_title: string;
|
||||
episode_description: string;
|
||||
}
|
||||
|
||||
export const PodcastEpisodeTableHeader = [
|
||||
{title: 'Importazione', value: 'imported'},
|
||||
{title: 'Titolo', value: 'title'},
|
||||
{title: 'Descrizione', value: 'short_description'},
|
||||
{title: 'Autore', value: 'author'},
|
||||
{title: 'Data di pubblicazione', value: 'pubDate'}
|
||||
];
|
||||
|
||||
export const getPodcastEpisodes = async (options: {
|
||||
podcast_id: Number;
|
||||
url: String;
|
||||
}): Promise<Array[]> => {
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.get(`/rss_podcast_episodes`, {params: filteredParams})
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
})
|
||||
}
|
||||
|
||||
export const downloadPodcastEpisode = async (options: {
|
||||
podcast_id: Number;
|
||||
podcast: Object;
|
||||
}): Promise => { //must add <Type>
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.post(`/podcast_episode`, {params: filteredParams})
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
})
|
||||
}
|
|
@ -2,7 +2,7 @@ import type {ShowInstance} from "@models/show/showInstance.ts";
|
|||
import type {ShowDays} from "@models/show/showDays";
|
||||
import type {ShowDJs} from "@models/show/showDJs.ts";
|
||||
import axios, {type AxiosResponse} from "axios";
|
||||
import {cleanOptions} from "@/helpers/AxiosHelper.ts";
|
||||
import {camelToSnake, cleanOptions} from "@/helpers/AxiosHelper.ts";
|
||||
|
||||
export interface Show {
|
||||
id?: number;
|
||||
|
@ -27,6 +27,9 @@ export interface Show {
|
|||
showDjs?: ShowDJs[];
|
||||
showInstances?: ShowInstance[];
|
||||
playlist?: any;
|
||||
|
||||
// Extra
|
||||
showType: 'show' | "spot" | null // Either show or spot
|
||||
}
|
||||
|
||||
export const baseShow = (): Show => {
|
||||
|
@ -46,6 +49,7 @@ export const baseShow = (): Show => {
|
|||
autoplaylistRepeat: false,
|
||||
showDjs: null,
|
||||
showDays: null,
|
||||
showType: null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +70,7 @@ export const getShows = async (options: {
|
|||
page?: Number | null;
|
||||
per_page?: Number | null;
|
||||
all?: string | null;
|
||||
showType?: 'show' | 'spot' | null;
|
||||
}): Promise<Show[]> => {
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.get(`/show`, {params: filteredParams})
|
||||
|
|
|
@ -17,6 +17,7 @@ export interface SmartBlock {
|
|||
contents?: SmartBlockContent[]; // Contenuti associati (opzionale)
|
||||
criteria?: SmartBlockCriteria[]; // Criteri associati (opzionale)
|
||||
tracks?: SmartBlockContent[];
|
||||
smart_block_type: 'show' | 'spot' | null;
|
||||
}
|
||||
|
||||
export const baseSmartBlock = (): SmartBlock => {
|
||||
|
@ -30,6 +31,7 @@ export const baseSmartBlock = (): SmartBlock => {
|
|||
contents: null,
|
||||
criteria: [],
|
||||
tracks: [],
|
||||
smart_block_type: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +48,7 @@ export const getSmartBlock = async (options: {
|
|||
page?: Number | null;
|
||||
per_page?: Number | null;
|
||||
all?: string | null;
|
||||
smartblockType?: 'show' | 'spot' | null;
|
||||
}): Promise<SmartBlock[]> => {
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.get(`/smartblock`, {params: filteredParams})
|
||||
|
|
96
resources/js/composables/content/models/webstream.ts
Normal file
96
resources/js/composables/content/models/webstream.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
import axios, {type AxiosResponse} from "axios";
|
||||
import {cleanOptions} from "@/helpers/AxiosHelper.ts";
|
||||
|
||||
export interface Webstream {
|
||||
id?: number;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
length?: string;
|
||||
creator_id?: number;
|
||||
mtime?: Date;
|
||||
utime?: Date;
|
||||
lptime?: Date;
|
||||
mime?: string;
|
||||
}
|
||||
|
||||
export const baseWebstream = (): Webstream => {
|
||||
return {
|
||||
id: null,
|
||||
name: 'test',
|
||||
description: '',
|
||||
url: '',
|
||||
length: '00:00:00',
|
||||
creator_id: null,
|
||||
mtime: null,
|
||||
utime: null,
|
||||
lptime: null,
|
||||
mime: null
|
||||
}
|
||||
}
|
||||
|
||||
export const webstreamTableHeader = [
|
||||
{title: 'Nome', value: 'name'},
|
||||
{title: 'Descrizione', value: 'description'},
|
||||
{title: 'URL', value: 'url'},
|
||||
{title: 'Durata', value: 'length'},
|
||||
{title: 'Azioni', value: 'actions'}
|
||||
]
|
||||
|
||||
export const getWebstreams = async (options: {
|
||||
name?: string | null;
|
||||
description?: string | null;
|
||||
url?: string | null;
|
||||
mime?: string | null;
|
||||
page?: Number | null;
|
||||
per_page?: Number | null;
|
||||
}): Promise<Webstream[]> => {
|
||||
const filteredParams = cleanOptions(options);
|
||||
return await axios.get(`/webstream`, {params: filteredParams})
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
})
|
||||
}
|
||||
|
||||
export const getWebstream = async (id: number): Promise<Webstream> => {
|
||||
return await axios.get(`/webstream/${id}`)
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
throw error;
|
||||
})
|
||||
}
|
||||
|
||||
export const createWebstream = async (webstream: Webstream): Promise<Webstream> => {
|
||||
return await axios.post('/webstream', webstream)
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
throw error;
|
||||
})
|
||||
}
|
||||
|
||||
export const updateWebstream = async (webstream: Webstream): Promise<Webstream> => {
|
||||
return await axios.put(`/webstream/${webstream.id}`, webstream)
|
||||
.then((response: AxiosResponse) => {
|
||||
return response.data
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
throw error;
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteWebstream = async (webstreamIds: Number[]) => {
|
||||
try {
|
||||
for (const webstreamId of webstreamIds) {
|
||||
await axios.delete(`/webstream/${webstreamId}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting webstream:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import {reactive, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import {timeFormatter} from "@/helpers/TimeFormatter.ts";
|
||||
import {useShowTypeStore} from "@stores/showType.store.ts";
|
||||
|
||||
export function playlist_page() {
|
||||
const items = ref([])
|
||||
|
@ -14,6 +15,7 @@ export function playlist_page() {
|
|||
'total_items': 0,
|
||||
'page': 1,
|
||||
})
|
||||
const showTypeStore = useShowTypeStore();
|
||||
|
||||
const headers = [
|
||||
// {title: '', key: 'artwork'},
|
||||
|
@ -30,7 +32,8 @@ export function playlist_page() {
|
|||
params: {
|
||||
page: page_info.page,
|
||||
per_page: page_info.itemsPerPage,
|
||||
all: search.value
|
||||
all: search.value,
|
||||
playlistType: showTypeStore.currentType,
|
||||
}
|
||||
}).then((response) => {
|
||||
console.log(response)
|
||||
|
@ -65,6 +68,8 @@ export function playlist_page() {
|
|||
item
|
||||
).then((response) => {
|
||||
console.log(response)
|
||||
}).catch((error) => {
|
||||
console.error("Error: "+error);
|
||||
})
|
||||
}
|
||||
|
||||
|
|
105
resources/js/composables/content/podcastEpisode_page.ts
Normal file
105
resources/js/composables/content/podcastEpisode_page.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import {reactive, ref} from "vue";
|
||||
import axios, {type AxiosResponse} from "axios";
|
||||
import {type PodcastEpisode, PodcastEpisodeTableHeader} from "@models/podcast/podcastEpisode.ts";
|
||||
import {DateTime} from "luxon";
|
||||
import {usePodcastStore} from "@/stores/podcast.store.ts";
|
||||
|
||||
export function podcast_episode_page(url: String) {
|
||||
const items = ref([]);
|
||||
const loading = ref(false);
|
||||
const listData = reactive({
|
||||
'itemsPerPage': 5,
|
||||
'first_page': null,
|
||||
'last_page': null,
|
||||
'total_items': 0,
|
||||
'page': 1,
|
||||
});
|
||||
const headers = PodcastEpisodeTableHeader;
|
||||
const downloadingEpisode = reactive({});
|
||||
|
||||
const importedPodcastEpisodes = async (podcast_id, element) => {
|
||||
return await axios.get('/podcast_episode', {
|
||||
params: {
|
||||
podcast_id: podcast_id
|
||||
}
|
||||
}).then( (response) => {
|
||||
// console.log(response.data);
|
||||
const episode = response.data.filter(imp => {
|
||||
if (imp.episode_guid === element.guid) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (episode.length > 0) {
|
||||
return (episode[0].file_id === null) ? -1 : episode[0].file_id;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
const getItems = async (load, podcast_id = 0) => {
|
||||
if (load) {
|
||||
loading.value = true;
|
||||
}
|
||||
return await axios.get(`/rss_podcast_load`, {
|
||||
params: {
|
||||
url: url
|
||||
}
|
||||
}).then( async (podcastEpisodesList: AxiosResponse) => {
|
||||
const episodes = podcastEpisodesList.data.episodes;
|
||||
items.value = episodes.map(element => {
|
||||
//element.imported = -1;
|
||||
element.short_description = '';
|
||||
if (typeof element.description === "string") {
|
||||
const arr = element.description.split(' ');
|
||||
const limit = arr.length >= 20 ? 20 : arr.length;
|
||||
for (let j = 0; j < limit; j++) {
|
||||
element.short_description += arr[j];
|
||||
element.short_description += (j === 19) ? '...' : ' ';
|
||||
}
|
||||
element.short_description = (element.short_description + '').replace(/&#\d+;/gm, function(s) {
|
||||
return String.fromCharCode(s.match(/\d+/gm)[0]);
|
||||
});
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
if (load) {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
return podcastEpisodesList.data.podcast.title;
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
});
|
||||
}
|
||||
|
||||
const downloadEpisode = async (podcast_id, item) => {
|
||||
return await axios.post('/podcast_episode', {
|
||||
podcast_id: podcast_id,
|
||||
episode: item,
|
||||
episode_url: item.enclosure["@attributes"].url,
|
||||
episode_title: item.title,
|
||||
}).then((response) => {
|
||||
console.log(response);
|
||||
}).catch((error) => {
|
||||
console.log("Error: "+error);
|
||||
});
|
||||
}
|
||||
|
||||
const checkDownloadEpisode = async (episode_id) => {
|
||||
return await axios.get(`/check_podcast_episode_download/${episode_id}`, {
|
||||
params: {
|
||||
|
||||
}
|
||||
}).then( (response) => {
|
||||
if (response.data === 'SUCCESS') {
|
||||
return true;
|
||||
}
|
||||
checkDownloadEpisode(episode_id);
|
||||
})
|
||||
}
|
||||
|
||||
getItems(true);
|
||||
|
||||
return { items, listData, headers, loading, downloadingEpisode, getItems, downloadEpisode }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue