feat(podcast): created ui, working on logics
This commit is contained in:
parent
baeb70dd46
commit
f042bf2140
18 changed files with 774 additions and 31 deletions
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));
|
||||
}
|
||||
}
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
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;
|
||||
|
||||
|
@ -14,9 +16,9 @@ class PodcastController extends Controller
|
|||
} else {
|
||||
$pagination = $request->per_page;
|
||||
}
|
||||
|
||||
return Podcast::searchFilter($request)
|
||||
->with(['episodes', 'imported'])
|
||||
->where('id', '!=', 1)
|
||||
->with(['episodes', 'imported', 'owner'])
|
||||
->paginate($pagination)
|
||||
->toJson();
|
||||
}
|
||||
|
@ -35,13 +37,28 @@ class PodcastController extends Controller
|
|||
return $this->save($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a smart block, cleaning also cc_blockcontent and cc_blockcriteria
|
||||
* @param $id
|
||||
* @return int
|
||||
*/
|
||||
public function delete($id) {
|
||||
return Podcast::with(['content', 'criteria'])::destroy($id);
|
||||
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 getEpisodes(Request $request)
|
||||
{
|
||||
$xml = simplexml_load_file($request->url, null, LIBXML_NOCDATA);
|
||||
$xmlArray = (array) $xml->channel;
|
||||
//episodes are stored in `item` array
|
||||
return $xmlArray['item'];
|
||||
}
|
||||
|
||||
protected function save(Request $request)
|
||||
|
@ -68,6 +85,9 @@ class PodcastController extends Controller
|
|||
'owner' => $user->id,
|
||||
])->save();
|
||||
|
||||
return $dbPodcast->toJson();
|
||||
return response()->json([
|
||||
'podcast' => $dbPodcast,
|
||||
'episodes' => $xml->channel->children('item', TRUE)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,32 +5,58 @@ namespace App\Http\Controllers;
|
|||
use App\Models\Podcast\Podcast;
|
||||
use App\Models\Podcast\PodcastEpisode;
|
||||
use Illuminate\Http\Request;
|
||||
use Celery\Celery;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Celery\Celery;
|
||||
|
||||
class PodcastEpisodeController extends Controller
|
||||
{
|
||||
private static $_CELERY_MESSAGE_TIMEOUT = 900000; // 15 minutes
|
||||
|
||||
public function downloadPodcastEpisode(Request $request) {
|
||||
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');
|
||||
}
|
||||
return $this->downloadPodcastEpisode($request);
|
||||
} catch (\Exception $exception) {
|
||||
return response($exception->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function downloadPodcastEpisode(Request $request) {
|
||||
$request->validate([
|
||||
'podcast_id' => 'required',
|
||||
'download_url' => 'required',
|
||||
'episode_url' => 'required',
|
||||
'episode_title' => 'required',
|
||||
]);
|
||||
|
||||
try {
|
||||
|
||||
$podcast = Podcast::find($request->podcast_id);
|
||||
$podcast = Podcast::findOrFail($request->podcast_id);
|
||||
|
||||
$podcastEpisode = new PodcastEpisode();
|
||||
$podcastEpisode->fill([
|
||||
'podcast_id' => $request->podcast_id,
|
||||
'publication_date' =>$request->publication_date,
|
||||
'download_url' => $request->download_url,
|
||||
'episode_guid' => $request->episode_guid,
|
||||
'episode_title' => $request->episode_title,
|
||||
'episode_description' => $request->episode_description,
|
||||
'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();
|
||||
|
||||
$conn = new Celery(
|
||||
|
@ -47,25 +73,33 @@ class PodcastEpisodeController extends Controller
|
|||
|
||||
$data = [
|
||||
'episode_id' => $podcastEpisode->id,
|
||||
'episode_url' => $podcastEpisode->url,
|
||||
'episode_url' => $request->episode_url,
|
||||
'episode_title' => $podcastEpisode->episode_title,
|
||||
'podcast_name' => $podcast->title,
|
||||
'override_album' => 'false' //ToDo connect $album_override from imported_podcast,
|
||||
];
|
||||
|
||||
$result = $conn->PostTask('tasks.download', $data);
|
||||
$result = $conn->PostTask('podcast-download', $data, true, 'podcast');
|
||||
|
||||
return $result->getId();
|
||||
|
||||
while (!$result->isReady()) {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
//Todo: throw exception
|
||||
throw new \Exception('podcast episode id:'.$podcastEpisode->id.' download failed');
|
||||
}
|
||||
//Todo: return ok
|
||||
return $result;
|
||||
// $podcastEpisode->fill([
|
||||
// ''
|
||||
// ]);
|
||||
} catch (\Exception $exception) {
|
||||
Log::error($exception->getMessage());
|
||||
die($exception->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use App\Filters\PlaylistFilter;
|
||||
use App\Filters\PodcastFilter;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Podcast extends Model
|
||||
{
|
||||
|
@ -47,7 +46,7 @@ class Podcast extends Model
|
|||
}
|
||||
|
||||
public function scopeSearchFilter($query, $request) {
|
||||
$filters = new PlaylistFilter();
|
||||
$filters = new PodcastFilter();
|
||||
return $filters->apply($query, $request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models\Podcast;
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
@ -22,6 +23,9 @@ class PodcastEpisode extends Model
|
|||
'episode_description',
|
||||
];
|
||||
|
||||
public function file() {
|
||||
return $this->hasOne(File::class);
|
||||
}
|
||||
public function podcast() {
|
||||
return $this->belongsTo(Podcast::class);
|
||||
}
|
||||
|
|
143
resources/js/components/content/Podcast.vue
Normal file
143
resources/js/components/content/Podcast.vue
Normal file
|
@ -0,0 +1,143 @@
|
|||
<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 bulk = ref(false);
|
||||
const dialog = reactive({
|
||||
open: false,
|
||||
type: '',
|
||||
title: '',
|
||||
text: ''
|
||||
});
|
||||
|
||||
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 = (confirm) => {
|
||||
if (confirm) {
|
||||
podcastStore.updateField({key: 'url', value: url});
|
||||
console.log(podcastStore);
|
||||
}
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PodcastEditor
|
||||
v-if="podcastStore.currentPodcast.url != '' && podcastStore.currentPodcast.url != null"
|
||||
@go-back="podcastStore.currentPodcast = basePodcast()"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
<VTextField
|
||||
label="Feed RSS"
|
||||
v-if="dialog.type === 'add'"
|
||||
v-model="url"
|
||||
/>
|
||||
</ConfirmDelete>
|
||||
</v-dialog>
|
||||
</template>
|
||||
</Table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
153
resources/js/components/content/partials/PodcastEditor.vue
Normal file
153
resources/js/components/content/partials/PodcastEditor.vue
Normal file
|
@ -0,0 +1,153 @@
|
|||
<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 {ref, watch} from "vue";
|
||||
import axios from "axios";
|
||||
|
||||
const auth = useAuthStore();
|
||||
|
||||
const emit = defineEmits([
|
||||
'saveItem'
|
||||
])
|
||||
|
||||
const podcastStore = usePodcastStore();
|
||||
const item = podcastStore.currentPodcast;
|
||||
console.log(item)
|
||||
|
||||
const podcastFields = podcast(item);
|
||||
const { items, headers, loading, downloadingEpisode, downloadEpisode } = podcast_episode_page(item.id, item.url);
|
||||
|
||||
const episodes = ref([]);
|
||||
|
||||
const checkError = (field, model) => {
|
||||
if (field.required) {
|
||||
// const error = field.required && (model === '' || model === null)
|
||||
// // disabledSaveButton.value = error
|
||||
// return error
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
watch(items, (newItems, oldItems) => {
|
||||
console.log(newItems, oldItems)
|
||||
if (item.id > 0) {
|
||||
axios.get('/podcast_episode', {
|
||||
params: {
|
||||
podcast_id: item.id
|
||||
}
|
||||
}).then((response) => {
|
||||
newItems.forEach((element) => {
|
||||
const episode = response.data.filter(imp => {
|
||||
if (imp.episode_guid === element.guid) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (episode.length > 0) {
|
||||
element.imported = (episode[0].file_id === null) ? null : episode[0].file_id;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log(newItems)
|
||||
episodes.value = newItems;
|
||||
}, {deep: true});
|
||||
|
||||
</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])"
|
||||
@update-property="updateProperty"
|
||||
: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 }">
|
||||
{{item.imported}}
|
||||
<v-icon
|
||||
class="me-2 spinning"
|
||||
size="small"
|
||||
v-if="item.imported === null"
|
||||
>
|
||||
mdi-loading
|
||||
</v-icon>
|
||||
<v-icon
|
||||
class="me-2"
|
||||
size="small"
|
||||
v-else-if="item.imported > 0"
|
||||
>
|
||||
mdi-check-outline
|
||||
</v-icon>
|
||||
<v-icon
|
||||
class="me-2 text-center"
|
||||
size="small"
|
||||
v-else
|
||||
@click="downloadEpisode(item)"
|
||||
>
|
||||
mdi-download-box
|
||||
</v-icon>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</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>
|
|
@ -15,6 +15,7 @@ console.log(props.bulk)
|
|||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p>{{ props.text }}</p>
|
||||
<slot></slot>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
|
@ -24,7 +25,7 @@ console.log(props.bulk)
|
|||
<v-btn
|
||||
color="accent"
|
||||
@click="$emit('confirm',false)"
|
||||
>Cancella</v-btn>
|
||||
>Annulla</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
|
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 = {
|
||||
name: {
|
||||
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);
|
||||
})
|
||||
}
|
86
resources/js/composables/content/podcastEpisode_page.ts
Normal file
86
resources/js/composables/content/podcastEpisode_page.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import {reactive, ref} from "vue";
|
||||
import axios, {type AxiosResponse} from "axios";
|
||||
import {type PodcastEpisode, PodcastEpisodeTableHeader} from "@models/podcast/podcastEpisode.ts";
|
||||
import {DateTime} from "luxon";
|
||||
|
||||
export function podcast_episode_page(podcast_id: Number, 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 () => {
|
||||
loading.value = true;
|
||||
console.log(url)
|
||||
return await axios.get(`/rss_podcast_episodes`, {
|
||||
params: {
|
||||
podcast_id: podcast_id,
|
||||
url: url
|
||||
}
|
||||
}).then( async (podcastEpisodesList: AxiosResponse) => {
|
||||
// console.log(podcastEpisodesList, podcast_id);
|
||||
const episodes = podcastEpisodesList.data;
|
||||
items.value = await episodes.map(element => {
|
||||
element.imported = -1;
|
||||
element.short_description = '';
|
||||
const arr = element.description.split(' ');
|
||||
for (let j = 0; j < 20; 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;
|
||||
});
|
||||
loading.value = false;
|
||||
}).catch((error: Error) => {
|
||||
console.log("Error: " + error);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
const downloadEpisode = async (item) => {
|
||||
console.log(item.enclosure["@attributes"].url)
|
||||
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);
|
||||
});
|
||||
}
|
||||
getItems();
|
||||
|
||||
return { items, listData, headers, loading, downloadingEpisode, downloadEpisode }
|
||||
}
|
80
resources/js/composables/content/podcast_page.ts
Normal file
80
resources/js/composables/content/podcast_page.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import {reactive, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import {timeFormatter} from "@/helpers/TimeFormatter.ts";
|
||||
import {deleteSmartBlock, getSmartBlock, SmartBlockTableHeader} from "@models/smartblock/smartblock.ts";
|
||||
import {showTableHeader} from "@models/show/show.ts";
|
||||
import {useAuthStore} from "@/stores/auth.store.ts";
|
||||
import {getPodcast, PodcastTableHeader} from "@models/podcast/podcast.ts";
|
||||
|
||||
export function podcast_page() {
|
||||
const items = ref([])
|
||||
const selected = ref([])
|
||||
const loading = ref(false)
|
||||
const search = ref('')
|
||||
const listData = reactive({
|
||||
'itemsPerPage': 5,
|
||||
'first_page': null,
|
||||
'last_page': null,
|
||||
'total_items': 0,
|
||||
'page': 1,
|
||||
})
|
||||
|
||||
const auth = useAuthStore();
|
||||
const timezone = auth.userData.timezone;
|
||||
|
||||
const headers = PodcastTableHeader;
|
||||
|
||||
const getItems = async (options) => {
|
||||
loading.value = true;
|
||||
return getPodcast({
|
||||
page: options.page,
|
||||
per_page: options.itemsPerPage,
|
||||
all: search.value
|
||||
}).then((podcastList) => {
|
||||
console.log(podcastList)
|
||||
listData.itemsPerPage = podcastList.per_page;
|
||||
listData.first_page = podcastList.from;
|
||||
listData.last_page = podcastList.last_page;
|
||||
listData.page = podcastList.current_page;
|
||||
listData.total_items = podcastList.total;
|
||||
|
||||
items.value = podcastList.data
|
||||
loading.value = false;
|
||||
|
||||
}).catch((error) => {
|
||||
console.log("Error: "+error);
|
||||
})
|
||||
}
|
||||
|
||||
const editItem = async (item) => {
|
||||
loading.value = true;
|
||||
let url = '/podcast'
|
||||
if (item.id > 0) {
|
||||
item['_method'] = 'PUT'
|
||||
url += `/${item.id}/`
|
||||
}
|
||||
|
||||
return await axios.post(
|
||||
url,
|
||||
item
|
||||
).then((response) => {
|
||||
console.log(response)
|
||||
loading.value = false
|
||||
return response.data
|
||||
}).catch((error) => {
|
||||
console.log("Error: "+error);
|
||||
})
|
||||
}
|
||||
|
||||
const deleteItem = (id) => {
|
||||
|
||||
return axios.post(`/podcast/${id}`, {
|
||||
_method: 'DELETE'
|
||||
}).then((response) => {
|
||||
getItems(listData)
|
||||
// items.value = response.status === 200 ? items.value.filter(obj => obj.id !== id) : items
|
||||
})
|
||||
}
|
||||
|
||||
return { items, listData, headers, selected, loading, search, getItems, editItem, deleteItem }
|
||||
}
|
|
@ -16,14 +16,15 @@ const tabs = {
|
|||
archive: defineAsyncComponent(() => import('@components/content/Archive.vue')),
|
||||
playlist: defineAsyncComponent(() => import('@components/content/Playlist.vue')),
|
||||
blocks: defineAsyncComponent(() => import('@components/content/SmartBlock.vue')),
|
||||
podcast: defineAsyncComponent(() => import('@components/content/Podcast.vue')),
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-col>
|
||||
<keep-alive>
|
||||
<!-- <keep-alive>-->
|
||||
<Component :is="tabs[currentPage]" />
|
||||
</keep-alive>
|
||||
<!-- </keep-alive>-->
|
||||
</v-col>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -24,7 +24,12 @@ const pages = [
|
|||
{
|
||||
id: 'blocks',
|
||||
name: 'Blocchi dinamici',
|
||||
component: '@components/content/Blocks.vue',
|
||||
component: '@components/content/SmartBlock.vue',
|
||||
},
|
||||
{
|
||||
id: 'podcast',
|
||||
name: 'Podcast',
|
||||
component: '@components/content/Podcast.vue',
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
|
19
resources/js/stores/podcast.store.ts
Normal file
19
resources/js/stores/podcast.store.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {defineStore} from "pinia";
|
||||
import {basePodcast, type Podcast} from "@models/podcast/podcast.ts";
|
||||
import type {PodcastEpisode} from "@models/podcast/podcastEpisode.ts";
|
||||
|
||||
export const usePodcastStore = defineStore('podcast', {
|
||||
state: () => ({
|
||||
currentPodcast: {} as Podcast,
|
||||
currentPodcastEpisodes: {} as PodcastEpisode,
|
||||
}),
|
||||
actions: {
|
||||
loadPodcast(podcastData: Podcast) {
|
||||
this.currentPodcast = { ...podcastData };
|
||||
this.currentPodcastEpisodes = podcastData.episodes;
|
||||
},
|
||||
updateField(payload: { key: string; value: any }) {
|
||||
this.currentPodcast[payload.key] = payload.value;
|
||||
},
|
||||
}
|
||||
})
|
|
@ -10,9 +10,8 @@ export const useSmartBlockStore = defineStore('smartblock', {
|
|||
actions: {
|
||||
async loadSmartBlock(smartblockData: SmartBlock) {
|
||||
this.currentSmartBlock = { ...smartblockData }
|
||||
this.currentSmartBlockCriteria = smartblockData.criteria
|
||||
},
|
||||
resetShowDays() {
|
||||
resetSmartBlock() {
|
||||
this.currentSmartBlock.showDays = { ...this.baseShowDays };
|
||||
},
|
||||
saveSmartBlock() {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Helpers\Preferences;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
use App\Http\Controllers\FileController;
|
||||
use App\Http\Controllers\PodcastController;
|
||||
use App\Http\Controllers\PodcastEpisodeController;
|
||||
use App\Http\Controllers\Show\ShowController;
|
||||
use App\Http\Controllers\Show\ShowDaysController;
|
||||
use App\Http\Controllers\ShowInstance\ShowInstancesController;
|
||||
|
@ -33,6 +34,8 @@ Route::resources([
|
|||
'file' => FileController::class,
|
||||
'track_type' => TrackTypeController::class,
|
||||
'playlist' => PlaylistController::class,
|
||||
'podcast' => PodcastController::class,
|
||||
'podcast_episode' => PodcastEpisodeController::class,
|
||||
'smartblock' => SmartBlockController::class,
|
||||
'show' => ShowController::class,
|
||||
'showDays' => ShowDaysController::class,
|
||||
|
@ -62,6 +65,7 @@ Route::get('/timezone', [Preferences::class, 'getTimezone']);
|
|||
*/
|
||||
Route::get('/test', [FileController::class, 'test']);
|
||||
Route::get('/testSchedule', [ShowInstancesController::class, 'testSchedule']);
|
||||
Route::get('/rss_podcast_episodes', [PodcastController::class, 'getEpisodes']);
|
||||
/**
|
||||
* CDDB Routes
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue