Mengatur Jadwal Posting dengan Agenda.js di PayloadCMS

15 Maret 2023 - Abdul Fattah Ikhsan

Bagikan:

Dalam era digital saat ini, membuat blog telah menjadi semakin mudah dengan adanya banyak platform yang tersedia. Salah satu platform yang bisa digunakan untuk membuat blog adalah PayloadCMS. PayloadCMS adalah sebuah CMS (Content Management System) open-source yang menyediakan berbagai fitur untuk mengelola konten, seperti blog posts, halaman, dan gambar. Saya sudah membahas tentang PayloadCMS di postingan sebelumnya. Dalam artikel ini, saya akan menjelaskan bagaimana cara membuat blog dengan PayloadCMS dan menjadwalkan postingan dengan Agenda.js.

Instalasi PayloadCMS

Langkah pertama untuk membuat blog dengan PayloadCMS adalah melakukan instalasi PayloadCMS terlebih dahulu. PayloadCMS dapat diunduh secara gratis melalui npmjs.com yang tercantum di situs resmi PayloadCMS. Setelah diunduh, langkah selanjutnya adalah melakukan instalasi PayloadCMS di server web Kalian. PayloadCMS memiliki dokumentasi yang lengkap dan mudah diikuti, sehingga Kalian tidak akan kesulitan dalam melakukan instalasi. Untuk memulai kita bisa menjalankannya di lokal komputer kita namun kalian harus menginstall Node.js, mongodb, dan Yarn atau NPM dulu. Kemudian jalankan perintah ini di Terminal atau command Prompt:

npx create-payload-app

Ikuti pertanyaan yang muncul di terminal. Contoh di lokal komputer saya seperti ini:

Instalasi PayloadCMS
Instalasi PayloadCMS

Pertanyaan-pertanyaan yang tampil antara lain (dalam bahasa indonesia):

  1. Apakah kita bersedia mengunduh create-payload-app. Ketik: y
  2. Pilih Nama proyek. Ketik: sesuai keinginan Kalian saja 😊. Saya menamakan payloadcms-lanjutkoding
  3. Pilih proyek template. ketik: pilih blog
  4. Masukkan koneksi MongoDB. ketik: alamat URI dari lokal atau remote MongoDB Kalian.
  5. Tunggu beberapa saat dan 🎉 Kita berhasil membuat blog dengan PayloadCMS.

Kemudian masuk ke direktori yang barus aja dibuat sekaligus menjalankannya.

cd payloadcms-lanjutkoding // sesuaikan dengan nama yang kalian buat
npm run dev

Beginilah struktur proyek dari blog template PayloadCMS:

struktur-proyek-payloadcms
Struktur proyek PayloadCMS

Bisa Kita lihat tempat nanti kita kerja ada di direktori src/ dan perlu dicatat bahwa PayloadCMS itu adalah Universal atau Isomorphic Platform maksudnya kode kita berjalan di server (Node.js) maupun di client (browser). Jadi hati-hati kalau menulis kode yang tidak bisa berjalan di browser, begitupun sebaliknya. Namun, ada 1 file yang khusus berjalan di server yaitu server.ts. Berikut struktur awal dari file tersebut:

// server.ts
import express from 'express';
import payload from 'payload';

require('dotenv').config();
const app = express();

// Redirect root to Admin panel
app.get('/', (_, res) => {
  res.redirect('/admin');
});

const start = async () => {
  // Initialize Payload
  await payload.init({
    secret: process.env.PAYLOAD_SECRET,
    mongoURL: process.env.MONGODB_URI,
    express: app,
    onInit: async () => {
      payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`)
    },
  })

  // Add your own express routes here

  app.listen(3000);
}

start();

Membuat Blog Post di PayloadCMS

Setelah PayloadCMS terinstal dengan baik, langkah selanjutnya adalah membuat blog post di PayloadCMS. Kalian harus membuat beberapa blog posts terlebih dahulu dan disimpan sebagai draft. PayloadCMS menyediakan editor yang mudah digunakan untuk membuat blog posts. Kalian bisa menambahkan gambar, video, dan teks pada blog posts Kalian. Selain itu, Kalian juga bisa menambahkan tag dan kategori untuk setiap blog post Kalian. Berikut tampilan form blog post bawaan dari PayloadCMS:

form-post-payloadcms
Form Post PayloadCMS

Hasil dari struktur awal post collection adalah seperti ini:

// Posts.ts - post collection
import { CollectionConfig } from 'payload/types';

const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    defaultColumns: ['title', 'author', 'category', 'tags', 'status'],
    useAsTitle: 'title',
  },
  access: {
    read: () => true,
  },
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'author',
      type: 'relationship',
      relationTo: 'users',
    },
    {
      name: 'publishedDate',
      type: 'date',
    },
    {
      name: 'category',
      type: 'relationship',
      relationTo: 'categories'
    },
    {
      name: 'tags',
      type: 'relationship',
      relationTo: 'tags',
      hasMany: true,
    },
    {
      name: 'content',
      type: 'richText'
    },
    {
      name: 'status',
      type: 'select',
      options: [
        {
          value: 'draft',
          label: 'Draft',
        },
        {
          value: 'published',
          label: 'Published',
        },
      ],
      defaultValue: 'draft',
      admin: {
        position: 'sidebar',
      }
    }
  ],
}

export default Posts;

Mengatur Jadwal Posting dengan Agenda.js

Setelah membuat blog posts, langkah selanjutnya adalah mengatur jadwal posting blog posts. PayloadCMS tidak memiliki fitur bawaan untuk mengatur jadwal posting, namun Kalian bisa menggunakan library JavaScript seperti Agenda.js untuk melakukan itu.

Agenda.js

Agenda.js adalah sebuah library JavaScript yang digunakan untuk mengatur jadwal pekerjaan (job scheduling) di Node.js. Dengan menggunakan Agenda.js, Kalian bisa membuat sebuah pekerjaan (job) untuk memposting blog posts pada waktu tertentu. Berikut adalah langkah-langkah yang perlu Kalian lakukan untuk mengatur jadwal posting blog posts dengan Agenda.js:

  1. Instalasi Agenda.js

Langkah pertama adalah melakukan instalasi Agenda.js. Kalian bisa melakukan instalasi Agenda.js menggunakan npm dengan mengetikkan perintah berikut di terminal:

npm install agenda
  1. Konfigurasi Agenda.js

Langkah selanjutnya adalah melakukan konfigurasi Agenda.js. Kalian perlu mengatur database untuk Agenda.js, misalnya MongoDB atau Redis. Kalian juga perlu mengatur job processor yang akan digunakan untuk mengeksekusi pekerjaan yang telah dijadwalkan. Berikut adalah contoh konfigurasi Agenda.js:

  • Definisikan job processornya. buat direktori dan file baru didalam src/ yaitu jobs/definitions.ts
// jobs/definitions.ts
import type Agenda from 'agenda';
import { type Payload } from 'payload';

const publishPostDefinition = (agenda: Agenda, payload: Payload) => {
    agenda.define('publish-post', {shouldSaveResult: true}, async (job) => {
        payload.logger.info(job.attrs, 'agenda.define publish-post job');
        // TODO: extract data & update post status from `draft` to `published`
    })
}

const allDefinitions = [publishPostDefinition];

export function allJobs(agenda: Agenda, payload: Payload) {
    allDefinitions.forEach(function(definition) {
        definition(agenda, payload);
    })
}

Pada contoh kode di atas, kita membuat sebuah pekerjaan dengan nama 'publish-post' yang akan mengeksekusi sebuah fungsi untuk menerbitkan blog posts. Fungsi ini akan menerima sebuah parameter dengan nama job yang akan diekstrak dan digunakan untuk memposting sebuah blog post nantinya.

Init konfigurasi Agenda.js di server.ts

// server.ts
import Agenda from 'agenda';
import express from 'express';
import payload from 'payload';

import { allJobs } from './jobs/definitions';

require('dotenv').config();
const app = express();

// Redirect root to Admin panel
app.get('/', (_, res) => {
  res.redirect('/admin');
});

const start = async () => {
  // Initialize Payload
  await payload.init({
    secret: process.env.PAYLOAD_SECRET,
    mongoURL: process.env.MONGODB_URI,
    express: app,
    onInit: async (p) => {
      p.logger.info(`Payload Admin URL: ${p.getAdminURL()}`)
      const agenda = new Agenda({
        db: {
          address: p.mongoURL as string,
          collection: 'agenda_jobs',
        },
        maxConcurrency: 8,
      })
      agenda.on('ready', async () => {
        allJobs(agenda, p)
        await agenda.start();
        p.logger.info('Agenda has been Started')
      })
      .on('error', (err) => {
        p.logger.info(err, "Agenda connection error")
      })
      app.set('agenda', agenda)
    },
  })

  // Add your own express routes here

  app.listen(3000);
}

start();

Pada server.ts, Kita menginisiasi Agenda.js dengan MongoDB URI yang sama dengan PayloadCMS didalam fungsi onInit dari PayloadCMS, dan memanggil job processor yang sudah kita buat ketika agenda sudah ready sekaligus start Agenda.js. Kemudian memasukkannya ke app setter dan getter dari express.js untuk memastikan Agenda.js hanya berjalan di server.

  1. Menjadwalkan Pekerjaan

Setelah melakukan konfigurasi Agenda.js, selanjutnya adalah menjadwalkan pekerjaan untuk memposting blog posts. Langkah selanjutnya Kita perlu ubah sedikit struktur Posts collection, seperti ini:

// Posts.ts Collection
const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    defaultColumns: ['title', 'author', 'category', 'tags', 'status'],
    useAsTitle: 'title',
  },
  access: {
    read: () => true,
  },
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'author',
      type: 'relationship',
      relationTo: 'users',
    },
    {
      name: 'publishedDate',
      type: 'date',
      admin: {
        hidden: true,
      }
    },
    {
      name: 'publishedSchedule',
      type: 'date',
      required: false,
      admin: {
        position: 'sidebar',
        date: {
          pickerAppearance: "dayAndTime",
          timeIntervals: 5,
        }
      }
    },
    {
      name: 'category',
      type: 'relationship',
      relationTo: 'categories'
    },
    {
      name: 'tags',
      type: 'relationship',
      relationTo: 'tags',
      hasMany: true,
    },
    {
      name: 'content',
      type: 'richText'
    },
    {
      name: 'status',
      type: 'select',
      options: [
        {
          value: 'draft',
          label: 'Draft',
        },
        {
          value: 'published',
          label: 'Published',
        },
      ],
      defaultValue: 'draft',
      admin: {
        position: 'sidebar',
      }
    }
  ],
}

export default Posts;

Kode diatas kita menambahkan property publishedSchedule yang akan kita gunakan untuk menjadwalkan di Agenda.js dan menyembunyikan publishedDate dari tampilan di admin dashboard karena akan otomatis terisi nanti ketika sudah diterbitkan. Karena Kita ubah struktur perlu menjalankan perintah ini untuk membuat typescript definitions yang berguna bagi kita kedepannya.

npm run generate:types
// optional
npm run generate:graphQLSchema

Kita cek, apa perubahan kita sudah berhasil:

form-post-payloadcms-2
Form Post Setelah perubahan struktur

Selanjutnya, tambahkan penjadwalan pekerjaan di dalam collection hook. Berikut adalah contoh kode untuk menjadwalkan pekerjaan:

// Posts.ts Collection
import type Agenda from 'agenda';
import { Post } from 'payload/generated-types';
import { compareAsc } from 'date-fns';
import {
  CollectionAfterChangeHook,
  CollectionBeforeChangeHook,
  CollectionConfig,
} from 'payload/types';

const afterChangeHook: CollectionAfterChangeHook<Post> = async ({req, operation, doc, previousDoc}) => {
  switch (operation) {
    case 'create':
    if (doc.status === 'draft' && doc.publishedSchedule && compareAsc(new Date(doc.publishedSchedule), new Date()) === 1) {
        const agenda = req.app.get('agenda') as Agenda;
        await agenda.schedule(new Date(doc.publishedSchedule), 'publish-post', {
          id: doc.id,
          title: doc.title
        })
      }
      break;
      case 'update':
      if (doc.status === 'draft' && doc.publishedSchedule && compareAsc(new Date(doc.publishedSchedule), new Date()) === 1) {
        const agenda = req.app.get('agenda') as Agenda;
        let agendaJobs = await agenda.jobs({
          name: 'publish-post',
          'data.id': doc.id,
        }, {_id: 1}, 1);
        if (agendaJobs.length === 1) {
          await agendaJobs[0].remove();
        }
        await agenda.schedule(new Date(doc.publishedSchedule), 'publish-post', {
          id: doc.id,
          title: doc.title
        })
      }
      break;

    default:
      break;
  }
  return doc;
}

const beforeChangeHook: CollectionBeforeChangeHook<Post> = async ({data, req, operation, originalDoc}) => {
  if (data.status === 'published') {
    data.publishedDate = new Date().toISOString();
  }
  return data;
}

const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    defaultColumns: ['title', 'author', 'category', 'tags', 'status'],
    useAsTitle: 'title',
  },
  access: {
    read: () => true,
  },
  hooks: {
    beforeChange: [beforeChangeHook],
    afterChange: [afterChangeHook]
  },
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'author',
      type: 'relationship',
      relationTo: 'users',
    },
    {
      name: 'publishedDate',
      type: 'date',
      admin: {
        hidden: true,
      }
    },
    {
      name: 'publishedSchedule',
      type: 'date',
      required: false,
      admin: {
        position: 'sidebar',
        date: {
          pickerAppearance: "dayAndTime",
          timeIntervals: 5,
        }
      }
    },
    {
      name: 'category',
      type: 'relationship',
      relationTo: 'categories'
    },
    {
      name: 'tags',
      type: 'relationship',
      relationTo: 'tags',
      hasMany: true,
    },
    {
      name: 'content',
      type: 'richText'
    },
    {
      name: 'status',
      type: 'select',
      options: [
        {
          value: 'draft',
          label: 'Draft',
        },
        {
          value: 'published',
          label: 'Published',
        },
      ],
      defaultValue: 'draft',
      admin: {
        position: 'sidebar',
      }
    }
  ],
}

export default Posts;

Pada contoh kode di atas, KIta menggunakan 2 built-in function hooks dari PayloadCMS.

  • Fungsi afterChangeHook adalah fungsi asynchronous yang dipicu setelah ada perubahan pada sebuah postingan. Fungsi ini memeriksa apakah postingan tersebut masih berupa draft dan memiliki jadwal publikasi di masa depan. Jika ya, maka fungsi ini menggunakan paket Agenda untuk menjadwalkan postingan untuk dipublikasikan pada tanggal dan waktu yang ditentukan dalam properti `publishedSchedule`. Kita juga menyertakan ID dari blog post yang akan diterbitkan sebagai data pekerjaan
  • Fungsi beforeChangeHook adalah fungsi asynchronous yang dipicu sebelum ada perubahan pada sebuah postingan. Fungsi ini memeriksa apakah postingan tersebut akan dipublikasikan dan menetapkan nilai pada field publishedDate menjadi tanggal dan waktu saat ini.

Terakhir Kita perlu perbarui job processor atau definisi pekerjaan yang ada di file jobs/definitions.ts agar Agenda.js bisa mengubah data post ketika sudah masuk waktu jadwal yang ditentukan. Contoh kodenya sebagai berikut:

// jobs/definitions.ts 
import type Agenda from 'agenda';
import { type Payload } from 'payload';

const publishPostDefinition = (agenda: Agenda, payload: Payload) => {
    agenda.define('publish-post', {shouldSaveResult: true}, async (job) => {
        payload.logger.info(job.attrs, 'agenda.define publish-post job');
        const { attrs: { data: { id } } } = job;
        let post = await payload.findByID({collection: 'posts', id, depth: 2});
        payload.logger.info({post}, 'agenda.define publish-post Post');
        if (post?.status === 'draft') {
            await payload.update({collection: 'posts', id, data: {status: 'published'}, depth: 2});
        }
    })
}

const allDefinitions = [publishPostDefinition];

export function allJobs(agenda: Agenda, payload: Payload) {
    allDefinitions.forEach(function(definition) {
        definition(agenda, payload);
    })
}

ID post berada di parameter job dan Kita mengakses nilainya melalui job.attrs.id . Setiap data yang dimasukkan di parameter ke 3 pada method agenda.schedule akan tersedia di objek job.attrs . Barulah Kita perbarui post status menjadi published dengan memastikan status sebelumnya adalah draft.

Dengan menggunakan Agenda.js, Kalian bisa menjadwalkan posting blog posts dengan mudah. Kalian juga bisa membuat lebih dari satu pekerjaan untuk memposting blog posts pada waktu yang berbeda.

Kesimpulan

PayloadCMS adalah sebuah CMS open-source yang menyediakan berbagai fitur untuk mengelola konten, termasuk blog posts. Meskipun PayloadCMS tidak memiliki fitur bawaan untuk mengatur jadwal posting blog posts, Kalian bisa menggunakan library JavaScript seperti Agenda.js untuk melakukan itu. Dengan Agenda.js, Kalian bisa dengan mudah menjadwalkan posting blog posts pada waktu yang diinginkan. Selamat mencoba!

Bagi kalian yang mau lihat langsung repositori proyeknya silakan ke sini. Saya terbuka bagi siapa saja yang ingin bertanya melalui instagram atau telegram saya, bisa lihat ke web ini.

#cms
#headless
#payloadcms
© 2021 - 2023 lanjutkoding.com