Tutorial Portal Berita - Level 1: Persiapan Proyek

Level 1 dari Tutorial Portal Berita berfokus pada membangun fondasi proyek kita. Bagian ini dirancang untuk diikuti dalam waktu sekitar 7 menit, mencakup langkah-langkah awal yang penting untuk mempersiapkan proyek portal berita kita untuk pengembangan.

Panduan Implementasi

Mari kita mulai mengimplementasikan aplikasi portal berita kita, dimulai dengan fitur-fitur penting di Level 1.

Persiapan Proyek

1. Buat Proyek Laravel Baru

Mari kita inisiasi proyek kita dengan menginstal Laravolt, yang akan membantu kita dalam membangun aplikasi portal berita kita.

composer create-project laravel/laravel news-portal
cd news-portal

Untuk memastikan riwayat code changes kita tersimpan dengan baik, kita akan menggunakan git untuk mengelola riwayat perubahan kode kita. Mari kita inisialisasi repositori git:

git init

Kita akan langsung membuat commit awal untuk menyimpan fresh Laravel project kita:

git add .
git commit -m "Initial commit: Fresh Laravel project"

2. Instal Laravolt

composer require laravolt/laravolt
php artisan laravolt:install

3. Show Off Code Coverage

Setelah menginstal aplikasi Laravolt, kita dapat menjalankan perintah berikut untuk menampilkan cakupan kode (code coverage):

XDEBUG_MODE=coverage php artisan test --coverage

Catatan: Pastikan Anda telah menginstal ekstensi PHP bernama XDebug.

Cakupan kode total dari instalasi baru Laravolt adalah 98,7%. Dengan memastikan cakupan kode yang tinggi, kita dapat merasa percaya diri bahwa kita memulai proyek kita dengan fondasi yang kuat.

4. Atur Lingkungan

Konfigurasikan file .env kita dengan pengaturan database yang sesuai:

DB_CONNECTION=sqlite

Sekarang, kita perlu menjalankan migrasi untuk membuat tabel yang diperlukan:

php artisan migrate

Mari kita lakukan commit untuk menyimpan perubahan ini:

git add .
git commit -m "Setup environment and database configuration"

Struktur Database

1. Buat Model dan Migrasi

Pertama, mari kita buat model dan juga migrasi yang diperlukan untuk proyek kita:

php artisan make:model Topic -m
php artisan make:model Post -m
php artisan make:model Comment -m

Laravel akan membuat file migrasi untuk setiap model yang kita buat. Mari kita perbarui file migrasi yang dihasilkan untuk mencakup kolom yang diperlukan.

2. Definisikan Skema Topik

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Jalankan migrasi.
     */
    // database/migrations/xxxx_xx_xx_create_topics_table.php
    public function up()
    {
        Schema::create('topics', function (Blueprint $table) {
            $table->ulid()->primary();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Balikkan migrasi.
     */
    public function down(): void
    {
        Schema::dropIfExists('topics');
    }
};

3. Definisikan Skema Post

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Jalankan migrasi.
     */
    // database/migrations/xxxx_xx_xx_create_posts_table.php
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->ulid()->primary();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->foreignUlid('topic_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('summary')->nullable();
            $table->longText('content');
            $table->string('featured_image')->nullable();
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Balikkan migrasi.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

4. Definisikan Skema Komentar

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Jalankan migrasi.
     */
    // database/migrations/xxxx_xx_xx_create_comments_table.php
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->ulid()->primary();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->foreignUlid('post_id')->constrained()->onDelete('cascade');
            $table->text('content');
            $table->boolean('is_approved')->default(false);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Balikkan migrasi.
     */
    public function down(): void
    {
        Schema::dropIfExists('comments');
    }
};

Manajemen Peran dan Izin

Karena kita akan menggunakan sistem ACL (Access Control List) bawaan Laravolt, mari kita atur peran dan izin yang diperlukan untuk aplikasi portal berita kita.

1. Buat Enum Izin

Perbarui enum Izin untuk menyertakan semua tindakan yang dapat dilakukan di portal berita kita:

<?php

namespace App\Enums;

use BenSampo\Enum\Enum;

final class Permission extends Enum
{
    // Akses dasbor
    const DASHBOARD_VIEW = 'dashboard.view';

    // Manajemen pos
    const POST_VIEW = 'post.view';

    const POST_CREATE = 'post.create';

    const POST_EDIT = 'post.edit';

    const POST_DELETE = 'post.delete';

    const POST_PUBLISH = 'post.publish';

    // Manajemen topik
    const TOPIC_VIEW = 'topic.view';

    const TOPIC_CREATE = 'topic.create';

    const TOPIC_EDIT = 'topic.edit';

    const TOPIC_DELETE = 'topic.delete';

    // Manajemen komentar
    const COMMENT_VIEW = 'comment.view';

    const COMMENT_CREATE = 'comment.create';

    const COMMENT_EDIT = 'comment.edit';

    const COMMENT_DELETE = 'comment.delete';

    const COMMENT_MODERATE = 'comment.moderate';

    // Manajemen pengguna
    const USER_VIEW = 'user.view';

    const USER_CREATE = 'user.create';

    const USER_EDIT = 'user.edit';

    const USER_DELETE = 'user.delete';
}

2. Sinkronkan Izin ke Database

Setelah mendefinisikan izin, kita perlu menyinkronkannya ke database:

php artisan laravolt:sync-permission

3. Buat Seeder Database untuk Peran

Buat seeder untuk menetapkan peran awal dan memberikan izin yang sesuai:

php artisan make:seeder AclSeeder

Perbarui seeder dengan definisi peran kita:

<?php

namespace Database\Seeders;

use App\Enums\Permission;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
use Laravolt\Platform\Models\Role;

class AclSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        // Buat peran
        $admin = Role::firstOrCreate(['name' => 'Administrator']);
        $writer = Role::firstOrCreate(['name' => 'Writer']);
        $member = Role::firstOrCreate(['name' => 'Member']);

        // Pastikan izin disinkronkan
        Artisan::call('laravolt:sync-permission');

        // Berikan semua izin kepada admin (wildcard)
        $admin->syncPermission(['*']);

        // Berikan izin tertentu kepada peran Penulis
        $writer->syncPermission([
            Permission::DASHBOARD_VIEW,
            Permission::POST_VIEW,
            Permission::POST_CREATE,
            Permission::POST_EDIT,
            Permission::POST_DELETE,
            Permission::COMMENT_VIEW,
            Permission::COMMENT_CREATE,
            Permission::COMMENT_EDIT,
            Permission::COMMENT_DELETE,
        ]);

        // Berikan izin tertentu kepada peran Anggota
        $member->syncPermission([
            Permission::COMMENT_VIEW,
            Permission::COMMENT_CREATE,
            Permission::COMMENT_EDIT,
            Permission::COMMENT_DELETE,
        ]);
    }
}

4. Perbarui Seeder Database

Tambahkan AclSeeder kita ke seeder database utama:

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            AclSeeder::class,
        ]);
    }
}

5. Buat Pengguna Uji Coba untuk Setiap Peran

Mari kita perbarui seeder kita untuk membuat pengguna uji coba untuk setiap peran:

php artisan make:seeder UserSeeder
<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        // Buat pengguna admin
        Artisan::call('laravolt:admin Administrator [email protected] secret');

        /** @var User */
        $admin = User::query()->where('email', '[email protected]')->first();
        $admin->assignRole('Administrator');

        // Buat pengguna penulis
        $writer = User::firstOrCreate(
            ['email' => '[email protected]'],
            [
                'name' => 'Content Writer',
                'password' => Hash::make('secret'),
                'status' => 'ACTIVE',
            ]
        );
        $writer->assignRole('Writer');

        // Buat pengguna anggota
        $member = User::firstOrCreate(
            ['email' => '[email protected]'],
            [
                'name' => 'Regular Member',
                'password' => Hash::make('password'),
                'status' => 'ACTIVE',
            ]
        );
        $member->assignRole('Member');
    }
}

Jangan lupa untuk menambahkan seeder ini ke kelas DatabaseSeeder.php juga.

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
            AclSeeder::class,
            UserSeeder::class,
        ]);
    }
}

7. Jalankan Seeder

Sekarang kita dapat mengisi database kita dengan peran awal, izin, dan pengguna uji coba:

php artisan db:seed

Dengan pengaturan ini, kita telah menetapkan sistem peran dan izin lengkap untuk aplikasi portal berita kita menggunakan ACL Laravolt. Aplikasi ini sekarang memiliki tiga peran yang berbeda (Administrator, Penulis, dan Anggota), masing-masing dengan izin yang sesuai. Kami juga telah membuat pengguna uji coba untuk memudahkan pengujian selama pengembangan.

Simpan Perubahan

git add .
git commit -m "Setup database and ACL seeder"

Jaminan Kualitas

Untuk memastikan kode kita memenuhi standart kualitas sejak awal, kita akan mengatur PHPStan untuk analisis statis dan Laravel Pint untuk perbaikan gaya penulisan kode otomatis.

1. Instal Dependensi

composer require --dev phpstan/phpstan larastan/larastan spaze/phpstan-disallowed-calls laravel/pint

Setup PHPStan

2. Buat File Konfigurasi

Buat file phpstan.neon di root proyek kita:

touch phpstan.neon
includes:
  - ./vendor/larastan/larastan/extension.neon
  - ./vendor/spaze/phpstan-disallowed-calls/extension.neon

parameters:
  paths:
    - app
    - config

  # The level 9 is the highest level
  level: 5

  treatPhpDocTypesAsCertain: false

  disallowedFunctionCalls:
    - function: 'env()'
      message: 'use config() instead'
      allowIn:
        - config/*.php

3. Jalankan Analisis

./vendor/bin/phpstan analyse

Catatan: Pastikan perintah berjalan dengan baik dan tidak ada kesalahan yang terlewat. Apabila ada error, tambahkan option --memory-limit=2G. Jadi perintah yang harus dijalankan ialah: ./vendor/bin/phpstan analyse --memory-limit=2G

Perbaiki masalah apa pun yang ditemukan PHPStan untuk memastikan kode kita memenuhi stkitar kualitas tinggi. Masalah umum meliputi:

  • Deklarasi tipe yang hilang
  • Metode yang tidak terdefinisi
  • Akses nilai yang mungkin null
  • Tipe yang tidak kompatibel dalam argumen

Setup GitHub Actions

Kita akan membuat 2 Job di GitHub Actions untuk menjalankan perbaikan otomatis gaya penulisan kode dan mengecek kualitas kode kita (PHPStan dan cakupan kode > 90%).

1. Code Style Fixes

Buat file .github/workflows/lint.yml di root proyek kita:

mkdir -p .github/workflows
touch .github/workflows/lint.yml
name: Linting

on:
  pull_request:
    branches:
      - develop
      - main

permissions:
  contents: write

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }} # This ensures we check out the PR branch

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'

      - name: Setup Bun
        uses: oven-sh/setup-bun@v1
        with:
          bun-version: latest

      - name: Install Dependencies
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

      - name: Run Pint
        run: vendor/bin/pint

      - name: Commit Changes
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          commit_message: 'fix: code style'
          commit_options: '--no-verify'
          branch: ${{ github.head_ref }} # Make sure changes are committed to the PR branch

2. Code Quality Check

Buat file .github/workflows/quality.yml di root proyek kita:

touch .github/workflows/quality.yml
name: Tests

on:
  push:
    branches:
      - develop
      - main
  pull_request:
    branches:
      - develop
      - main

jobs:
  ci:
    runs-on: ubuntu-latest
    container:
      image: ramaid/image:php8.4-fullstack-cli-v2.5.0
      options: --user root
      volumes:
        - /run/docker:/run/docker

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # Fetch all history for all branches and tags
          fetch-depth: 0

      - name: Install Dependencies
        run: composer install --no-interaction --prefer-dist --optimize-autoloader

      - name: Setup Environment
        run: |
          cp .env.example .env
          php artisan migrate --seed --force
          php artisan key:generate
          php artisan laravolt:link

      - name: Static Analysis
        run: vendor/bin/phpstan analyse

      - name: Tests
        run: |
          XDEBUG_MODE=coverage php artisan test --coverage \
            --coverage-clover=coverage.xml \
            --log-junit=results.xml

    #   - name: SonarCloud Scan
    #     uses: SonarSource/sonarqube-scan-action@v5
    #     env:
    #       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    #       SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

    #   - name: Sonar Scan Self Hosted
    #     env:
    #       SONAR_TOKEN: ${{ secrets.SONAR_MC_TOKEN }}
    #     run: |
    #       sonar-scanner -Dsonar.token=$SONAR_TOKEN -Dsonar.host.url=http://sonar.malescast.tech/

Langkah Selanjutnya

Panduan implementasi di atas mencakup pengaturan awal struktur database, model, dan kebijakan otorisasi untuk portal berita kita. Pada bagian selanjutnya dari tutorial ini (yang akan segera ditambahkan), kita akan membahas:

  1. Menyiapkan AutoCRUD untuk panel admin
  2. Membangun situs web yang dihadapi publik dengan TailwindCSS
  3. Mengimplementasikan sistem komentar
  4. Membuat grafik dasbor dengan Laravolt Chart
  5. Mengembangkan fitur-fitur yang lebih canggih seperti ekspor dan penyaringan
  6. Menambahkan notifikasi email
  7. Mengimplementasikan pengaturan situs web dan pengalihan tema
  8. Mengoptimalkan kinerja dan kualitas