Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/Http/Controllers/CMS/ArticleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public function store(CreateArticleRequest $request)
}
$this->articleRepository->create($input);

(new \App\Services\ArtikelService)->clearAllCache();

Session::flash('success', 'Artikel berhasil disimpan.');

return redirect(route('articles.index'));
Expand Down Expand Up @@ -135,6 +137,8 @@ public function update($id, UpdateArticleRequest $request)
}
$article = $this->articleRepository->update($input, $id);

(new \App\Services\ArtikelService)->clearAllCache();

Session::flash('success', 'Artikel berhasil diupdate.');

return redirect(route('articles.index'));
Expand All @@ -159,6 +163,9 @@ public function destroy($id)
$this->authorize('delete', $article);

$this->articleRepository->delete($id);

(new \App\Services\ArtikelService)->clearAllCache();

if (request()->ajax()) {
return $this->sendSuccess('Artikel berhasil dihapus.');
}
Expand Down
84 changes: 60 additions & 24 deletions app/Http/Controllers/Web/ArtikelController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\CMS\Article;
use App\Services\ArtikelService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\View\View;

class ArtikelController extends Controller
{
Expand All @@ -16,62 +20,94 @@ public function __construct(ArtikelService $artikelService)
}

/**
* Tampilkan daftar artikel OpenSID.
* Tampilkan daftar artikel gabungan (OpenKab & OpenSID).
*
* @param Request $request
* @return \Illuminate\View\View
* @return View
*/
public function index(Request $request)
public function index(Request $request): View
{
$search = $request->get('search', '');
$categoryId = $request->get('kategori', '');

$filters = [];
if (!empty($search)) {
$filters['search'] = $search;
$filters['filter[search]'] = $search;
}
if (!empty($categoryId)) {
$filters['kategori'] = $categoryId;
$filters['filter[id_kategori]'] = $categoryId;
}

// Ambil data melalui service
// Format Pagination API json format
$filters['page[number]'] = $request->get('page', 1);
$filters['page[size]'] = 6;
$filters['sort'] = '-tgl_upload'; // Terurut berdasarkan tanggal terbaru
// Ambil data gabungan melalui service
$combinedArticles = $this->artikelService->getCombinedArticles($filters);

// Caching ditangani oleh ArtikelService
$articles = $this->artikelService->artikel($filters);
// Pagination menggunakan LengthAwarePaginator
$page = max((int) $request->get('page', 1), 1);
$perPage = 6;
$offset = ($page - 1) * $perPage;
$itemsForCurrentPage = $combinedArticles->slice($offset, $perPage)->values();

// Filter out disabled articles just in case API returns them
$articles = $articles->filter(function ($item) {
return isset($item->enabled) && $item->enabled == 1;
});
$articles = new LengthAwarePaginator(
$itemsForCurrentPage,
$combinedArticles->count(),
$perPage,
$page,
['path' => $request->url(), 'query' => $request->query()]
);

return view('web.artikel.index', [
'title' => 'Artikel Berita',
'articles' => $articles,
'search' => $search,
'categoryId' => $categoryId
'categoryId' => $categoryId,
]);
}

/**
* Tampilkan detail artikel OpenSID.
* Endpoint JSON untuk widget Artikel Terbaru di Beranda.
*
* @param int $id
* @return \Illuminate\View\View
* @param Request $request
* @return JsonResponse
*/
public function terbaru(Request $request): JsonResponse
{
$limit = max((int) $request->get('limit', 6), 1);
$articles = $this->artikelService->getArtikelTerbaru($limit);

return response()->json([
'status' => 'success',
'data' => $articles,
]);
}

/**
* Tampilkan detail artikel OpenSID (dengan fallback ke artikel CMS lokal jika ID/slug cocok).
*
* @param int|string $id
* @return View|\Illuminate\Http\RedirectResponse
*/
public function show($id)
{
$article = $this->artikelService->artikelById($id);
$article = is_numeric($id) ? $this->artikelService->artikelById((int) $id) : null;

if (!$article || !isset($article->enabled) || $article->enabled == 0) {
abort(404, 'Artikel tidak ditemukan atau tidak aktif');
if ($article && isset($article->enabled) && $article->enabled == 1) {
return view('web.artikel.show', [
'object' => $article,
]);
}

return view('web.artikel.show', [
'object' => $article
]);
// Fallback: Cek apakah ID/slug merujuk pada artikel CMS OpenKab lokal
$localArticle = Article::where('id', $id)
->orWhere('slug', $id)
->first();

if ($localArticle && $localArticle->state == Article::PUBLISH && $localArticle->published_at <= now()) {
return redirect()->route('article', ['aSlug' => $localArticle->slug]);
}

abort(404, 'Artikel tidak ditemukan atau tidak aktif');
}
}

136 changes: 113 additions & 23 deletions app/Services/ArtikelService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace App\Services;

use App\Models\CMS\Article;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use stdClass;

class ArtikelService extends BaseApiService
Expand Down Expand Up @@ -33,7 +35,51 @@ private function registerCacheKey(string $key): void
}

/**
* Mendapatkan daftar artikel dengan filter opsional
* Mendapatkan daftar artikel dari CMS lokal OpenKab
*
* @param array<int|string, mixed> $filters
*/
public function getLocalArticles(array $filters = []): Collection
{
$search = $filters['search'] ?? $filters['filter[search]'] ?? null;
$categoryId = $filters['kategori'] ?? $filters['category_id'] ?? $filters['filter[id_kategori]'] ?? null;

$query = Article::with('category')
->where('state', Article::PUBLISH)
->where('published_at', '<=', now());

if (! empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('content', 'like', "%{$search}%");
});
}

if (! empty($categoryId)) {
$query->where('category_id', $categoryId);
}

$articles = $query->orderBy('published_at', 'desc')->get();

return $articles->map(function (Article $article): stdClass {
return (object) [
'id' => $article->id,
'slug' => $article->slug,
'judul' => $article->title,
'isi' => $article->content,
'gambar' => $article->thumbnail ? Storage::url($article->thumbnail) : null,
'id_kategori' => $article->category_id,
'kategori_nama' => $article->category?->name ?? 'Berita OpenKab',
'tgl_upload' => $article->published_at ? $article->published_at->format('Y-m-d H:i:s') : ($article->created_at ? $article->created_at->format('Y-m-d H:i:s') : ''),
'enabled' => 1,
'source' => 'openkab',
'detail_url' => route('article', ['aSlug' => $article->slug]),
];
});
}

/**
* Mendapatkan daftar artikel dari API upstream OpenSID dengan filter opsional
*
* @param array<int|string, mixed> $filters
*/
Expand All @@ -46,33 +92,72 @@ public function artikel(array $filters = []): Collection

// Ambil dari cache dulu
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($filters): Collection {
$data = $this->apiRequest('/api/v1/artikel/list', $filters);
try {
$data = $this->apiRequest('/api/v1/artikel/list', $filters);

if (empty($data)) {
if (empty($data)) {
return collect([]);
}

return collect($data)->map(function (array $item): stdClass {
// Return 'attributes' but with 'id' populated
$attributes = $item['attributes'] ?? [];
$attributes['id'] = $item['id'] ?? null;
$attributes['source'] = 'opensid';
$attributes['detail_url'] = isset($attributes['id']) ? route('web.artikel.show', $attributes['id']) : '#';

// Fetch detail to enrich with gambar and isi if missing
if (isset($attributes['id']) && (! isset($attributes['gambar']) || ! isset($attributes['isi']))) {
$detail = $this->artikelById((int) $attributes['id']);
if ($detail !== null) {
$attributes['gambar'] = $detail->gambar ?? null;
$attributes['isi'] = $detail->isi ?? null;
}
}

return (object) $attributes;
});
} catch (\Throwable $e) {
return collect([]);
}
});
}

return collect($data)->map(function (array $item): stdClass {
// Return 'attributes' but with 'id' populated
$attributes = $item['attributes'] ?? [];
$attributes['id'] = $item['id'] ?? null;

// Fetch detail to enrich with gambar and isi if missing
if (isset($attributes['id']) && (! isset($attributes['gambar']) || ! isset($attributes['isi']))) {
$detail = $this->artikelById((int) $attributes['id']);
if ($detail !== null) {
$attributes['gambar'] = $detail->gambar ?? null;
$attributes['isi'] = $detail->isi ?? null;
}
}
/**
* Mendapatkan gabungan artikel OpenKab lokal dan artikel OpenSID API, terurut berdasarkan tanggal terbaru
*
* @param array<int|string, mixed> $filters
*/
public function getCombinedArticles(array $filters = []): Collection
{
$cacheKey = $this->buildCacheKey('artikel_combined', $filters);
$this->registerCacheKey($cacheKey);

return (object) $attributes;
return Cache::remember($cacheKey, $this->cacheTtl, function () use ($filters): Collection {
$localArticles = $this->getLocalArticles($filters);
$apiArticles = $this->artikel($filters);

// Filter out disabled articles just in case
$apiArticles = $apiArticles->filter(function ($item) {
return isset($item->enabled) && $item->enabled == 1;
});

return $localArticles->concat($apiArticles)->sortByDesc(function ($item) {
return $item->tgl_upload ?? '1970-01-01 00:00:00';
})->values();
});
}

/**
* Mendapatkan detail artikel berdasarkan ID
* Mendapatkan daftar artikel terbaru gabungan untuk widget beranda
*/
public function getArtikelTerbaru(int $limit = 6): Collection
{
return $this->getCombinedArticles(['page[size]' => $limit])->take($limit)->values();
}

/**
* Mendapatkan detail artikel berdasarkan ID (dari API)
*/
public function artikelById(int $id): ?stdClass
{
Expand All @@ -82,12 +167,16 @@ public function artikelById(int $id): ?stdClass
$this->registerCacheKey($cacheKey);

return Cache::remember($cacheKey, $this->cacheTtl, function () use ($id): ?stdClass {
$data = $this->apiRequest('/api/v1/artikel/tampil', [
'id' => $id,
]);
try {
$data = $this->apiRequest('/api/v1/artikel/tampil', [
'id' => $id,
]);

if (is_array($data) && count($data) > 0) {
return (object) $data;
if (is_array($data) && count($data) > 0) {
return (object) $data;
}
} catch (\Throwable $e) {
return null;
}

return null;
Expand Down Expand Up @@ -141,3 +230,4 @@ public function clearAllCache(): void
Cache::forget($this->cacheRegistryKey);
}
}

2 changes: 2 additions & 0 deletions catatan_rilis.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ Di rilis ini, versi 2608.0.2 berisi penambahan dan perbaikan yang diminta penggu

#### Penambahan Fitur

1. [#1268](https://github.com/OpenSID/OpenKab/issues/1268) Penambahan fitur artikel tidak tampil di website.

#### Perbaikan BUG

1. [#1105](https://github.com/OpenSID/OpenKab/issues/1105) Perbaikan filter kecamatan tampil tanpa memilih kabupaten terlebih dahulu.
Expand Down
Loading