Skip to content
2 changes: 1 addition & 1 deletion app/Helpers/general.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function csp_nonce()
*/
function openkab_versi()
{
return 'v2608.0.2';
return 'v2609.0.0';
}
}

Expand Down
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);
}
}

12 changes: 4 additions & 8 deletions catatan_rilis.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
Di rilis ini, versi 2608.0.2 berisi penambahan dan perbaikan yang diminta pengguna.
Di rilis ini, versi 2609.0.0 berisi penambahan dan perbaikan yang diminta pengguna.

#### Penambahan Fitur

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

1. [#1105](https://github.com/OpenSID/OpenKab/issues/1105) Perbaikan filter kecamatan tampil tanpa memilih kabupaten terlebih dahulu.
2. [#1108](https://github.com/OpenSID/OpenKab/issues/1108) Perbaikan filter tahun dan desa masih ada yang kurang di data sandang.
3. [#1111](https://github.com/OpenSID/OpenKab/issues/1111) Perbaikan style pada halaman slide tertutupi card icon penduduk.
4. [#1262](https://github.com/OpenSID/OpenKab/issues/1262) Perbaikan grafik tidak tampil pada Profile Kependudukan - Kesehatan.
5. [#1260](https://github.com/OpenSID/OpenKab/issues/1260) Perbaikan Error 500 gagal ambil data statistik partisipasi sekolah dan ijazah tertinggi.
6. [#1265](https://github.com/OpenSID/OpenKab/issues/1265) perbaikan data desa tidak tampil di OpenKab.
#### Perbaikan BUG

#### Perubahan Teknis


1. [#1270](https://github.com/OpenSID/OpenKab/issues/1270) Perbaiki seeder data identitas.
16 changes: 8 additions & 8 deletions database/seeders/IdentitasSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ class IdentitasSeeder extends Seeder
public function run()
{
$defaultData = [
'nama_aplikasi' => 'Simatik',
'nama_kabupaten' => 'KOTA BIMA',
'kode_kabupaten' => '52.72',
'nama_provinsi' => 'Nusa Tenggara Barat',
'kode_provinsi' => '52',
'sebutan_kab' => 'Kota'
'nama_aplikasi' => 'OpenKab',
'nama_kabupaten' => 'Belum Ditentukan',
'kode_kabupaten' => '00.00',
'nama_provinsi' => 'Belum Ditentukan',
'kode_provinsi' => '00',
'sebutan_kab' => 'Kabupaten'
];
if(Schema::hasColumn('identitas', 'sebutan_desa')){
$defaultData['sebutan_desa'] = 'Kelurahan';
$defaultData['sebutan_desa'] = 'Desa';
}
return Identitas::create($defaultData);
Identitas::create($defaultData);
}
}
Loading
Loading