|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Administracion;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Models\Noticia;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
|
|
class NoticiaController extends Controller
|
|
|
|
|
{
|
|
|
|
|
// GET /api/noticias
|
|
|
|
|
public function index(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$perPage = (int) $request->get('per_page', 9);
|
|
|
|
|
|
|
|
|
|
$query = Noticia::query();
|
|
|
|
|
|
|
|
|
|
if ($request->filled('publicado')) {
|
|
|
|
|
$query->where('publicado', $request->boolean('publicado'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('categoria')) {
|
|
|
|
|
$query->where('categoria', (string) $request->get('categoria'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($request->filled('q')) {
|
|
|
|
|
$q = trim((string) $request->get('q'));
|
|
|
|
|
$query->where(function ($sub) use ($q) {
|
|
|
|
|
$sub->where('titulo', 'like', "%{$q}%")
|
|
|
|
|
->orWhere('descripcion_corta', 'like', "%{$q}%");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $query
|
|
|
|
|
->orderByDesc('destacado')
|
|
|
|
|
->orderByDesc('fecha_publicacion')
|
|
|
|
|
->orderByDesc('orden')
|
|
|
|
|
->orderByDesc('id')
|
|
|
|
|
->paginate($perPage);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $data->items(), // incluye imagen_url por el accessor/appends
|
|
|
|
|
'meta' => [
|
|
|
|
|
'current_page' => $data->currentPage(),
|
|
|
|
|
'last_page' => $data->lastPage(),
|
|
|
|
|
'per_page' => $data->perPage(),
|
|
|
|
|
'total' => $data->total(),
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GET /api/noticias/{noticia}
|
|
|
|
|
public function show(Noticia $noticia)
|
|
|
|
|
{
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $noticia, // incluye imagen_url por el accessor/appends
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GET /api/noticias-publicas/{noticia} (o la ruta que uses)
|
|
|
|
|
public function showPublic(Noticia $noticia)
|
|
|
|
|
{
|
|
|
|
|
abort_unless($noticia->publicado, 404);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $noticia,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// POST /api/noticias (multipart/form-data si viene imagen)
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$data = $request->validate([
|
|
|
|
|
'titulo' => ['required', 'string', 'max:220'],
|
|
|
|
|
'slug' => ['nullable', 'string', 'max:260', 'unique:noticias,slug'],
|
|
|
|
|
'descripcion_corta' => ['nullable', 'string', 'max:500'],
|
|
|
|
|
'contenido' => ['nullable', 'string'],
|
|
|
|
|
'categoria' => ['nullable', 'string', 'max:80'],
|
|
|
|
|
'tag_color' => ['nullable', 'string', 'max:30'],
|
|
|
|
|
|
|
|
|
|
// ✅ dos formas de imagen
|
|
|
|
|
'imagen' => ['nullable', 'file', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
|
|
|
|
|
'imagen_url' => ['nullable', 'url', 'max:600'],
|
|
|
|
|
|
|
|
|
|
'link_url' => ['nullable', 'url', 'max:600'],
|
|
|
|
|
'link_texto' => ['nullable', 'string', 'max:120'],
|
|
|
|
|
'fecha_publicacion' => ['nullable', 'date'],
|
|
|
|
|
'publicado' => ['nullable', 'boolean'],
|
|
|
|
|
'destacado' => ['nullable', 'boolean'],
|
|
|
|
|
'orden' => ['nullable', 'integer'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// slug por defecto (igual tu modelo lo genera, pero aquí lo dejamos por consistencia)
|
|
|
|
|
if (empty($data['slug'])) {
|
|
|
|
|
$data['slug'] = Str::slug($data['titulo']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// si viene archivo, manda a storage y prioriza archivo
|
|
|
|
|
if ($request->hasFile('imagen')) {
|
|
|
|
|
$path = $request->file('imagen')->store('noticias', 'public');
|
|
|
|
|
$data['imagen_path'] = $path;
|
|
|
|
|
$data['imagen_url'] = null; // ✅ evita conflicto con url externa
|
|
|
|
|
} else {
|
|
|
|
|
// si viene imagen_url externa, no debe haber imagen_path
|
|
|
|
|
$data['imagen_path'] = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// si publican sin fecha, poner ahora
|
|
|
|
|
if (!empty($data['publicado']) && empty($data['fecha_publicacion'])) {
|
|
|
|
|
$data['fecha_publicacion'] = now();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$noticia = Noticia::create($data);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $noticia->fresh(),
|
|
|
|
|
], 201);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PUT/PATCH /api/noticias/{noticia}
|
|
|
|
|
public function update(Request $request, Noticia $noticia)
|
|
|
|
|
{
|
|
|
|
|
$data = $request->validate([
|
|
|
|
|
'titulo' => ['sometimes', 'required', 'string', 'max:220'],
|
|
|
|
|
'slug' => ['sometimes', 'nullable', 'string', 'max:260', 'unique:noticias,slug,' . $noticia->id],
|
|
|
|
|
'descripcion_corta' => ['sometimes', 'nullable', 'string', 'max:500'],
|
|
|
|
|
'contenido' => ['sometimes', 'nullable', 'string'],
|
|
|
|
|
'categoria' => ['sometimes', 'nullable', 'string', 'max:80'],
|
|
|
|
|
'tag_color' => ['sometimes', 'nullable', 'string', 'max:30'],
|
|
|
|
|
|
|
|
|
|
// ✅ dos formas de imagen
|
|
|
|
|
'imagen' => ['sometimes', 'nullable', 'file', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
|
|
|
|
|
'imagen_url' => ['sometimes', 'nullable', 'url', 'max:600'],
|
|
|
|
|
|
|
|
|
|
'link_url' => ['sometimes', 'nullable', 'url', 'max:600'],
|
|
|
|
|
'link_texto' => ['sometimes', 'nullable', 'string', 'max:120'],
|
|
|
|
|
'fecha_publicacion' => ['sometimes', 'nullable', 'date'],
|
|
|
|
|
'publicado' => ['sometimes', 'boolean'],
|
|
|
|
|
'destacado' => ['sometimes', 'boolean'],
|
|
|
|
|
'orden' => ['sometimes', 'integer'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Si llega imagen archivo, reemplaza la anterior y limpia imagen_url externa
|
|
|
|
|
if ($request->hasFile('imagen')) {
|
|
|
|
|
if ($noticia->imagen_path && Storage::disk('public')->exists($noticia->imagen_path)) {
|
|
|
|
|
Storage::disk('public')->delete($noticia->imagen_path);
|
|
|
|
|
}
|
|
|
|
|
$path = $request->file('imagen')->store('noticias', 'public');
|
|
|
|
|
|
|
|
|
|
$data['imagen_path'] = $path;
|
|
|
|
|
$data['imagen_url'] = null; // ✅ prioridad archivo
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Si llega imagen_url (externa), borra la imagen física anterior y limpia imagen_path
|
|
|
|
|
if (array_key_exists('imagen_url', $data) && !empty($data['imagen_url'])) {
|
|
|
|
|
if ($noticia->imagen_path && Storage::disk('public')->exists($noticia->imagen_path)) {
|
|
|
|
|
Storage::disk('public')->delete($noticia->imagen_path);
|
|
|
|
|
}
|
|
|
|
|
$data['imagen_path'] = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Si explícitamente mandan imagen_url = null (quitar url) y no mandan archivo,
|
|
|
|
|
// no tocamos imagen_path (se queda como está). Si quieres que también limpie path,
|
|
|
|
|
// dime y lo cambiamos.
|
|
|
|
|
|
|
|
|
|
// si se marca publicado y no hay fecha, set now
|
|
|
|
|
if (
|
|
|
|
|
array_key_exists('publicado', $data) &&
|
|
|
|
|
$data['publicado'] &&
|
|
|
|
|
empty($noticia->fecha_publicacion) &&
|
|
|
|
|
empty($data['fecha_publicacion'])
|
|
|
|
|
) {
|
|
|
|
|
$data['fecha_publicacion'] = now();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// si cambian titulo y slug no vino, regenerar slug (opcional)
|
|
|
|
|
if (array_key_exists('titulo', $data) && !array_key_exists('slug', $data)) {
|
|
|
|
|
$data['slug'] = Str::slug($data['titulo']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$noticia->update($data);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'data' => $noticia->fresh(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DELETE /api/noticias/{noticia}
|
|
|
|
|
public function destroy(Noticia $noticia)
|
|
|
|
|
{
|
|
|
|
|
if ($noticia->imagen_path && Storage::disk('public')->exists($noticia->imagen_path)) {
|
|
|
|
|
Storage::disk('public')->delete($noticia->imagen_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$noticia->delete();
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'success' => true,
|
|
|
|
|
'message' => 'Noticia eliminada correctamente',
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|