Fotos, Laravel y VueJS

Para dar la opción al usuario de poder subir fotos a la aplicación Web he utilizado el componente vue-media-upload de Saimow.

Instalo mediante npm

npm install vue-media-upload

El componente funciona subiendo las imágenes con Axios a una carpeta temporal cada vez que añadimos una. Luego si queremos guardar las imágenes hay que pasarlas a la carpeta final.

Definimos la ruta para subir las carpetas a la ruta temporal:

Route::post('/media/upload', [MediaController::class, 'uploadMedia'])-> name('media.upload')->;

En la página Vue:

import Uploader from 'vue-media-upload';

// los datos de las imágenes obtenidas las guardaré a través del componente useForm de Inertia
const form = useForm({
    _method: 'POST',
    ...
    media: {},
});

// Definimos los eventos necesarios del Uploader para actualizar los datos de las imágenes añadidas o eliminadas
const addMedia = (addedImage, addedMedia) => {
    form.media.added = addedMedia
}
const removeMedia = (removedImage, removedMedia) => {
    form.media.removed = removedMedia
}

// Al montar el componente Vue vamos a obtener las imagenes
onMounted(() => {
    getMedia();

});
const getMedia = () => {
    axios.get(route('ruta.get.media', form.id))
        .then(response => {
            form.media = {saved: [], added:[], removed:[]};
            form.media.saved = response.data.media;
            form.defaults('media', form.media);  // Para quitar el aviso de que hay cambios pendientes de grabar.

            hasMediaResponse.value = true;  // Aquí es donde vamos a dar la señal para generar el componente Uploader
        })
}

...

<Uploader
    v-if="hasMediaResponse"
    :server="route('media.upload')"
    :media="form.media.saved"
    location="/media"
    @add="addMedia"
    @remove="removeMedia"
/>

Es importante el v-if para que solo se genere el componente cuando ya ha obtenido las imágenes del servidor, sino, no se actualizará.

A continuación vamos a completar el controlador encargado de subir y grabar las fotos:

public function getMedia(Request $request, int $work_id)
{
    $media = Media::
        select('type', 'file_name as name')->
        whereModelId($work_id)->get();

    return response()->json(['media' => $media]);
}

// Se llama cuando al componente se le asigna una imagen, la sube a una carpeta temporal
public function uploadMedia(Request $request)
{
    if ($request->hasFile('image'))
    {
        // Guardo en carpeta temporal
        $file = $request->file('image');
        $name = uniqid() . '_' . $file->getClientOriginalName();

        $file->storeAs('tmp', $name);

        return ['name' => $name];            
    }
}

// Grabar el modelo con las imágenes
public function update(Request $request)
{
    ...
    $model->save();

    $this->saveTemporaryFilesToMedia($model->id, $request->media['added']);
    $this->removeFilesFromMedia($model->id, $request->media['removed']);

    return redirect(route('...'));
}

// Guarda las imágenes temporales que finalmente se hayan grabado
private function saveTemporaryFilesToMedia(int $model_id, array $media)
{
    foreach($media as $image){
        $from = storage_path('app/tmp/' . $image['name']);
        $to = storage_path('app/media/' . $image['name']);

        File::move($from, $to);

        $media = Media::create([
            'model_name' => Work::class,
            'model_id' => $model_id,
            'type' => '',
            'file_name' => $image['name'],
        ]);
    }
}

// Elimina las imagenes guardadas que se han borrado (no las temporales)
private function removeFilesFromMedia(int $model_id, array $media)
{
    foreach($media as $image) {
        File::delete('app/media/' . $image['name']);

        Media::
            whereModelName(Work::class)->
            whereModelId($model_id)->
            whereFileName($image['name'])->
            delete();
    }
}

Las imágenes las guardo en la carpeta storage la cual no es pública, por lo que debo crear una función en el controlador para obtenerlas. Primero defino la ruta:

Route::get('/media/{file_name}', [MediaController::class, 'getFile'])->name('media.get');

Y su definición en el controlador:

public function getFile(Request $request, string $fileName)
{
    return response()->file(storage_path('app/media/') . $fileName);
}

Por último nos faltaría generar un cron para que todas las noches se borraran todas las imágenes de la carpeta temporal, eso para más adelante.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *