Skip to content

[MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY] Sinkronisasi On-Demand Kapal Inaportnet, Optimasi Evaluasi Status Superset Summary, Refaktorisasi Summary Status Service V2, dan Mitigasi Keamanan Brace-Expansion ​

Modul: Pengangkut | Tipe: Refactoring | Stage Saat Ini: STAGING | Tanggal Dokumen: 2026-09-04


1. Informasi Tugas & Referensi Bisnis ​

  • Task ID / Nomor Referensi: MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY

  • Sumber Instruksi: Git Branch feat/update-kapal, feat/superset-summary-optimizations, refactor/post-status-helper, fix/security-override-brace-expansion

  • Dokumen Referensi / Lampiran: Tidak ada

  • Task / Dependensi Terkait (Cross-Service): REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION

  • Modul Terkait: Pengangkut

  • Service / Repository: pengangkut-backend

  • PIC Pengembang: Hermansyah Efendi

  • Latar Belakang & Ringkasan Kebutuhan: Rangkaian pemeliharaan, refaktorisasi arsitektur status tahapan layanan, dan mitigasi keamanan library pada modul Pengangkut. Perubahan mencakup 4 perbaikan arsitektur dan bisnis yang krusial:

    1. Sinkronisasi On-Demand Kapal Inaportnet (feat/update-kapal): Pengadaan endpoint sinkronisasi spesifikasi kapal dari API Inaportnet untuk kapal bertrayek (id_route_type > 0) guna memperbaiki inkonsistensi data identitas dan spesifikasi teknis kapal.
    2. Resolusi Blocker Re-submit & Bypass Status Cetakan Manifest (feat/superset-summary-optimizations):
      • Penambahan status kode 47 (Reject Kelengkapan Data Inward), 48 (Reject Kelengkapan Data Outward), dan 49 (Reject Kelengkapan Data RKSP) ke dalam ALLOWED_STATUS_BY_SERVICE agar pengguna dapat mengedit dan mengirim ulang permohonan (canSend: true) setelah ditolak kelengkapan datanya oleh Bea Cukai.
      • Implementasi mekanisme IGNORED_STATUS_BY_SERVICE untuk status 44 (Cetakan Manifest). Status 44 merupakan aksi pencetakan dokumen PDF perantara dan bukan tahapan persetujuan operasional, sehingga di-bypass pada kalkulasi status terakhir agar tidak menutupi status riil permohonan.
    3. Standardisasi Status & Controller V2 (refactor/post-status-helper): Sentralisasi kamus master status seluruh domain pengangkut (iw, ow, rksp, cop, phqc, wkk, wkb) ke dalam app/constant/statuses.constant.js, migrasi handler POST /summary/status ke V2 service layer terpadu (summaryStatusService), serta pemberian anotasi @deprecated pada fungsi legacy postStatus dan postStatusAsync di superset_summary_controller.js.
    4. Mitigasi Keamanan DoS (fix/security-override-brace-expansion): Penerapan npm package override brace-expansion: 5.0.8 untuk menutup celah keamanan Denial of Service (GHSA-mh99-v99m-4gvg).
  • Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):

    • [x] Sub-Pekerjaan 1 — Integrasi Sinkronisasi Kapal Trayek: Implementasi #mapInaportToRawPayload dan #prepareShipDataForSaving pada ship.service.js serta routing endpoint POST /super-set/ship/inaport-sync/:serviceNumber
    • [x] Sub-Pekerjaan 2 — Optimasi State Engine Superset Summary:
      • Konfigurasi IGNORED_STATUS_BY_SERVICE untuk melewati status 44 (Cetakan Manifest) pada evaluasi getLatestStatus dan getSummaryByServiceNumber
      • Konfigurasi ALLOWED_STATUS_BY_SERVICE untuk membuka izin kirim ulang (canSend) pada status 47 (Reject Kelengkapan IW), 48 (Reject Kelengkapan OW), dan 49 (Reject Kelengkapan RKSP)
    • [x] Sub-Pekerjaan 3 — Sentralisasi Kamus Status & Layanan: Pembentukan modul konstanta app/constant/statuses.constant.js (master status COP, IW, OW, PHQC, RKSP, WKB, WKK), service-type.constant.js, dan submission-type.constant.js
    • [x] Sub-Pekerjaan 4 — Arsitektur Controller V2 Post Status: Implementasi summaryStatusService.updateStatus yang mendukung single & array payload, pembuatan controller V2 app/controllers/v2/summary.controller.js, re-routing POST /summary/status, serta deprecasi fungsi legacy postStatus dan postStatusAsync di superset_summary_controller.js
    • [x] Sub-Pekerjaan 5 — Hardening Security Dependensi: Penguncian versi library brace-expansion pada versi 5.0.8 di package.json dan pembaruan dependency tree package-lock.json

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-07-27 10:31Hermansyah EfendiDEPLOYEDBranch: development (Merged from 4 maintenance branches)
2Staging (UAT)2026-09-09 11:34achmad fauziDEPLOYEDRelease Candidate v1.18.2 via MR !2080. Co-deployed: [TASK-DRAGONFLYDB-MIGRATION, TASK-SPLIT-BILLING-202608, FEAT-MANUAL-BILLING-REQUEST, MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY, REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION, TASK-PINO-LOGGER-REFACTOR, ND-304-LNSW.34-2026, ND-256-LNSW.34-2026, ND-183-LNSW.34-2026]
3Production——PENDING—

Riwayat Update Log ​

TanggalEnvironmentPerubahanPIC
2026-09-09StagingPromosi ke STAGING via MR !2080 (Release Candidate v1.18.2)Hermansyah Efendi
2026-09-04DevelopmentInitial log created from backend context blockHermansyah Efendi

3. Analisis Teknis & Rincian Perubahan ​

  • Alur Logika & Bisnis:

    • Pencegahan Data Stale pada Kapal Bertrayek (ship.service.js):
      • Kapal bertrayek (id_route_type > 0) memiliki master data yang terdaftar di Inaportnet. Method syncShipFromInaport memungkinkan operator menyinkronkan data permohonan secara on-demand langsung dari Inaportnet API jika terjadi perubahan nama kapal, call sign, tonase (GT/DWT), atau pelabuhan asal/tujuan.
      • Untuk permohonan non-trayek (id_route_type <= 0), permintaan ditolak dengan pesan yang eksplisit (400 Bad Request).
    • Kalkulasi State Dashboard Superset (superset-summary.service.js):
      • Selama ini, saat pengguna mengunduh atau mencetak manifest, status berubah menjadi 44 (Cetakan Manifest). Hal ini menyebabkan query evaluasi status terakhir menganggap dokumen masih dalam proses cetak dan mengaburkan status definitif Bea Cukai.
      • Dengan filter AND hsd.status_id NOT IN (:ignoredStatuses) pada getLatestStatus serta filtering di getSummaryByServiceNumber, status 44 di-skip sehingga status riil sebelum/sesudah cetakan tetap terbaca dengan benar.
      • Pengguna yang mendapat penolakan kelengkapan dokumen Bea Cukai (status 47, 48, 49) kini memiliki hak akses untuk memicu pengiriman ulang dokumen (resubmission) tanpa harus membuat nomor layanan permohonan baru dari awal (canSend: true).
    • Penyatuan Endpoint Status (summary-status.service.js & v2/summary.controller.js):
      • Pembaruan status permohonan dikonsolidasikan ke method updateStatus, memvalidasi tipe layanan dan kode status terhadap kamus statuses.constant.js.
      • Menggunakan pattern asyncHandler dan validasi Zod schema (SummaryPostStatusValidationSchema).
    • Hardening Security Dependensi (package.json):
      • Menambahkan blok override pada package.json guna memaksa resolusi dependensi brace-expansion ke versi patch aman 5.0.8 untuk menutup celah DoS (GHSA-mh99-v99m-4gvg).
  • Matriks Pemetaan Status Kunci (Business Domain Context):

    LayananKode StatusNama StatusKategori & Perlakuan Sistem
    IW44Cetakan ManifestBypass (IGNORED_STATUS_BY_SERVICE): Dilewati pada kalkulasi status agar tidak menutupi status persetujuan/penolakan riil.
    IW47Reject Kelengkapan Data InwardWhitelisted (ALLOWED_STATUS_BY_SERVICE): Membuka izin edit & kirim ulang permohonan (canSend: true).
    OW44Cetakan ManifestBypass (IGNORED_STATUS_BY_SERVICE): Dilewati pada kalkulasi status.
    OW48Reject Kelengkapan Data OutwardWhitelisted (ALLOWED_STATUS_BY_SERVICE): Membuka izin edit & kirim ulang permohonan (canSend: true).
    RKSP44Cetakan ManifestBypass (IGNORED_STATUS_BY_SERVICE): Dilewati pada kalkulasi status.
    RKSP49Reject Kelengkapan Data RKSPWhitelisted (ALLOWED_STATUS_BY_SERVICE): Membuka izin edit & kirim ulang permohonan (canSend: true).
    COP / PHQC44Cetakan Billing PembayaranCetakan billing Karantina Kesehatan Kemenkes.
  • Daftar File yang Berubah:

    • Controller / API Route:
      • app/controllers/v2/ship.controller.js (Handler syncShipFromInaport)
      • app/controllers/v2/summary.controller.js (Handler V2 postStatus berbasis asyncHandler)
      • app/controllers/kedatangan_inward_controller.js (Migrasi pemanggilan status ke summaryStatusService)
      • app/controllers/superset_summary_controller.js (Deprecate fungsi legacy postStatus dan postStatusAsync)
      • app/routers/ship_route.js (Registrasi route POST /super-set/ship/inaport-sync/:serviceNumber)
      • app/routers/superset_summary_route.js (Re-route POST /summary/status ke V2)
    • Service & Constants:
      • app/services/ship.service.js (Metode #mapInaportToRawPayload dan syncShipFromInaport)
      • app/services/superset-summary.service.js (Whitelist status 47, 48, 49 & bypass status 44)
      • app/services/summary-status.service.js (Core engine pembaruan status terpusat)
      • app/constant/statuses.constant.js (Kamus konstanta status lengkap 7 layanan pengangkut)
      • app/constant/service-type.constant.js (Enum kode layanan pengangkut)
      • app/constant/submission-type.constant.js (Enum tipe kedatangan / keberangkatan)
    • Security & Config:
      • package.json & package-lock.json (Override dependensi brace-expansion)
      • CHANGELOG.md (Pencatatan rilis perubahan)
  • Perubahan Database (DDL / DML):

    Tidak ada perubahan skema database (DDL) atau file migrasi SQL pada seluruh branch ini.

  • Kontrak API & Response Payload:

    POST /super-set/ship/inaport-sync/:serviceNumber ​

    • Deskripsi: Sinkronisasi on-demand spesifikasi dan identitas kapal dari Inaportnet API ke database lokal.
    • Autentikasi / Guard: Bearer Token (doIntrospection) + checkAccessShipByServiceNumber
    • Request Header / Query / Params:
      • Header: Authorization: Bearer <token>
      • Path Param: serviceNumber (string, nomor permohonan AJU)
    • Request Body: Tidak ada (eksekusi via path parameter serviceNumber)
    • Response Payload (200 OK):
      json
      {
        "status": "Success",
        "code": 200,
        "message": "Data kapal berhasil disinkronkan dari Inaport",
        "data": {
          "id_ship": 105,
          "service_number": "101026060001",
          "name": "KM BINTANG SAMUDERA",
          "flag": "ID",
          "gross_tonnage": 1500,
          "deadweight_ton": 2200,
          "call_sign": "YBD2",
          "updated_at": "2026-06-03T08:54:02.000Z"
        }
      }
    • Response Error (400 Bad Request):
      json
      {
        "status": "Failed",
        "code": 400,
        "message": "Sync dari Inaport hanya berlaku untuk kapal trayek!"
      }

    POST /summary/status (V2 Controller) ​

    • Deskripsi: Memperbarui status tahapan layanan pengangkut secara terpusat (mendukung single update via HTTP controller dan batch/array via service layer).
    • Autentikasi / Guard: Bearer Token (doIntrospection)
    • Request Header / Query / Params:
      • Header: Authorization: Bearer <token>, Content-Type: application/json
      • Query: showAll=true|false (mengembalikan full breakdown status), type=string
    • Request Body (Contoh Single Update):
      json
      {
        "service_number": "101026060001",
        "service_type": "iw",
        "status_id": 47,
        "status_info": "Reject Kelengkapan Data Inward"
      }
    • Request Body (Opsi Batch Array via Service Layer):
      json
      [
        "101026060001",
        "iw",
        47,
        "Reject Kelengkapan Data Inward"
      ]
    • Response Payload (200 OK):
      json
      {
        "status": "Success",
        "code": 200,
        "message": "Status berhasil diperbarui",
        "data": {
          "serviceNumber": "101026060001",
          "serviceType": "iw",
          "statusId": 47,
          "statusInfo": "Reject Kelengkapan Data Inward"
        }
      }
    • Response Error (400 Bad Request):
      json
      {
        "status": "Failed",
        "code": 400,
        "message": "service number is cannot to be empty"
      }

4. Bukti Teknis (Technical Proof & Audit Trail) ​

Daftar Commit Terkait (Multi-Commit Trail) ​

Commit IDBranchAuthorTanggal & JamRingkasan Perubahan
3bf76f0bd94cf79e504333f7d9a7502730f6a116fix/security-override-brace-expansionHermansyah Efendi2026-07-27 10:31fix(security): override brace-expansion to v5.0.8 to fix DoS vulnerability
dc9883dd70ffb6b906b3b6d6459bb3eac80d9159refactor/post-status-helperHermansyah Efendi2026-07-07 15:25refactor(summary): migrate status update logic to service layer and v2 controller
cdb0b3e9867cc40da5d6f9c2150acc34ebd3daccrefactor/post-status-helperHermansyah Efendi2026-07-07 15:11feat(constant): add service, status, and submission type constants
27fa8bb78e8f3aaed22bb2e90141ef06dcdc0756feat/superset-summary-optimizationsHermansyah Efendi2026-06-17 15:53refactor(superset-summary): centralize allowed statuses and add ignored status bypass
d30edb3cf2561b83fd9e9e9ab127b17efe8ca480feat/update-kapalHermansyah Efendi2026-06-03 08:54feat(ship): add sync ship from inaport endpoint and optimize upsert override

Cuplikan Kode / Implementasi Inti ​

1. Penanganan Whitelist & Bypass Status Transisi ​
javascript
// app/services/superset-summary.service.js
const STATUS_IW_REJECT_COMPLETENESS = 47;
const STATUS_OW_REJECT_COMPLETENESS = 48;
const STATUS_RKSP_REJECT_COMPLETENESS = 49;

const ALLOWED_STATUS_BY_SERVICE = {
	iw: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED, STATUS_IW_REJECT_COMPLETENESS],
	ow: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED, STATUS_OW_REJECT_COMPLETENESS],
	rksp: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED, STATUS_RKSP_REJECT_COMPLETENESS],
	wkk: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED],
	wkb: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED, STATUS_SEND_FAILED],
	cop: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED],
	phqc: [STATUS_DRAFT, STATUS_START, STATUS_REJECTED],
};

// Status 44 (Cetakan Manifest) di-bypass agar tidak menimpa status operasional sebelumnya
const IGNORED_STATUS_BY_SERVICE = {
	iw: [44],
	ow: [44],
	rksp: [44],
};
2. Logika Sinkronisasi Kapal Inaportnet ​
javascript
// app/services/ship.service.js
async syncShipFromInaport(serviceNumber, auth, headers) {
    const userId = auth.userId;

    const dataAgent = await agentService.findOneByServiceNumber(serviceNumber);
    if (!dataAgent) throw new NotFoundError('AJU tidak ditemukan!');

    if (dataAgent.id_route_type <= 0) {
        throw new BadRequestError('Sync dari Inaport hanya berlaku untuk kapal trayek!');
    }

    const trayek = this.getTrayekNameById(dataAgent.id_route_type);
    const existingShip = await this.findOneByServiceNumber(serviceNumber);

    const registrationNumber = dataAgent.registration_ship_number;
    const ship = await this.postDataFunction(trayek, registrationNumber, userId, headers, auth);

    const rawPayload = {
        serviceNumber,
        trayek,
        registrationNumber,
        identity: {},
        specification: {},
        ports: {},
        dates: {},
        isOutward: serviceNumber.startsWith('102'),
        userId
    };

    this.#mapInaportToRawPayload(ship, rawPayload);
    const preparedData = this.#prepareShipDataForSaving(rawPayload);

    await Ship.update({
        ...preparedData,
        updated_by: userId,
        updated_at: new Date()
    }, {
        where: { id_ship: existingShip.id_ship }
    });

    return await this.getDataShipSpecific({ serviceNumber });
}
3. Controller V2 Update Status ​
javascript
// app/controllers/v2/summary.controller.js
export const postStatus = asyncHandler(async (req, res) => {
    await parseWithZod(SummaryPostStatusValidationSchema, req.body);

    const { service_number, service_type, status_id, status_info } = req.body;
    const { showAll, type } = req.query;

    const data = await summaryStatusService.updateStatus({
        service_number,
        service_type,
        status_id,
        status_info,
        type,
    });

    if (showAll === 'true') {
        req.query.serviceNumber = service_number;
        return getAllSummaryByServiceNumber(req, res);
    }

    res.sendJson(summaryStatusService.humanizeResult(data));
});
4. Mitigasi Keamanan Dependensi (Override brace-expansion) ​
json
// package.json
"overrides": {
  "uuid": "^14.0.0",
  "brace-expansion": "^5.0.8"
}

5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification) ​

A. Verifikasi Unit Test (Development Stage) ​

(Opsional: Dilampirkan jika task mencakup penambahan/perubahan unit test)

  • Test Suite File: Tidak ada file unit test baru disertakan pada commit pemeliharaan ini
  • Status Eksekusi CLI: SKIPPED
text
// Tidak ada file unit test baru disertakan pada commit pemeliharaan ini.
// Pengujian difokuskan pada integrasi manual via Postman dan QA Playbook.

B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook) ​

Diisi saat development sebagai panduan eksekusi pengujian QA, kemudian dilengkapi bukti response JSON saat verifikasi di Staging.

  • Cakupan / Surface Pengujian: API / Postman Only & Full E2E (UI + Backend)
  • Kebutuhan Akses / Role Akun: Token sesi aktif, akun agen pelayaran berwenang, permohonan kapal trayek (id_route_type > 0), dan permohonan kapal non-trayek (id_route_type <= 0).
  • Hasil Pengujian UAT: PENDING
  • Catatan QA: —
Skenario Pengujian QA (Test Scenarios) ​
  1. Skenario 1 — Happy Path Sinkronisasi Kapal Trayek dari Inaportnet:

    • Pre-kondisi: Permohonan AJU kapal trayek terdaftar dengan status aktif (id_route_type > 0).
    • Trigger / Request: POST /super-set/ship/inaport-sync/:serviceNumber dengan Bearer Token valid.
    • Ekspektasi Response: HTTP 200 OK dengan pesan "Data kapal berhasil disinkronkan dari Inaport".
    • Verifikasi Database / Log: Record pada tabel pengangkut.ships ter-update dengan atribut mutakhir dari Inaportnet (updated_at bernilai waktu eksekusi).
  2. Skenario 2 — Negative Case Sinkronisasi Kapal Non-Trayek:

    • Pre-kondisi: Permohonan AJU memiliki id_route_type <= 0 (Kapal Non-Trayek).
    • Trigger / Request: POST /super-set/ship/inaport-sync/:serviceNumber.
    • Ekspektasi Response: HTTP 400 Bad Request dengan pesan error: "Sync dari Inaport hanya berlaku untuk kapal trayek!".
    • Verifikasi Database / Log: Tidak ada perubahan record pada tabel pengangkut.ships.
  3. Skenario 3 — Validasi Bypass Status 44 (Cetakan Manifest) pada Superset Summary:

    • Pre-kondisi: Permohonan memiliki riwayat status 40 (Disetujui), kemudian sistem mencatat status 44 (Cetakan Manifest).
    • Trigger / Request: Panggil endpoint summary GET /summary?serviceNumber=:serviceNumber.
    • Ekspektasi Response: Ringkasan status layanan menampilkan status 40, bukan status 44, karena status 44 terdaftar pada IGNORED_STATUS_BY_SERVICE.
    • Verifikasi Database / Log: Query summary otomatis mengecualikan status transisi (status_id NOT IN (44)).
  4. Skenario 4 — Izin Kirim Ulang Permohonan Saat Status 47/48/49 (Reject Kelengkapan Data):

    • Pre-kondisi: Permohonan Inward mendapatkan status 47 (Reject Kelengkapan Data Inward).
    • Trigger: Evaluasi properti canSend pada summary response permohonan.
    • Ekspektasi: canSend bernilai true (sebelumnya bernilai false), sehingga tombol kirim ulang di antarmuka web aktif dan pengguna dapat melakukan submit perbaikan.
    • Verifikasi Database / Log: Logika evaluasi status merujuk pada elemen baru STATUS_IW_REJECT_COMPLETENESS di array ALLOWED_STATUS_BY_SERVICE.iw.
  5. Skenario 5 — Pembaruan Status Layanan Menggunakan Controller V2:

    • Pre-kondisi: Permohonan aktif dalam status awal.
    • Trigger / Request: POST /summary/status dengan payload JSON { service_number, service_type: "iw", status_id: 47, status_info: "Reject Kelengkapan Data Inward" }.
    • Ekspektasi Response: HTTP 200 OK dengan pesan "Status berhasil diperbarui".
    • Verifikasi Database / Log: Record status tersimpan pada tabel tracking log permohonan.
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PENDING",
  "note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend