Files
desa-darmasaba/QC/DESA/fix-summary-profil-desa.md
nico b9b00f0a20 docs(qc): add quality control summaries for various modules
Added comprehensive QC reports and fix summaries for:
- Desa (Berita, Potensi, Profil, Layanan, Penghargaan, Pengumuman)
- Kesehatan (Posyandu)
- Landing Page (APBDes, SDGS, Anti-Korupsi, Profil, Prestasi)
- PPID (Daftar Informasi, Dasar Hukum, IKM, Permohonan, Struktur, Visi Misi)
2026-04-23 12:11:55 +08:00

9.3 KiB

Fix Summary - Profil Desa High Priority Issues

Tanggal: 25 Februari 2026
Status: Partially Completed


COMPLETED FIXES

1. Schema - deletedAt @default(now()) Bug FIXED

File: prisma/schema.prisma

Changes:

// BEFORE
model SejarahDesa {
  deletedAt DateTime @default(now())  // ❌ BUG
}

// AFTER
model SejarahDesa {
  deletedAt DateTime?  // ✅ FIXED
}

Affected Models:

  • SejarahDesa
  • VisiMisiDesa
  • LambangDesa
  • MaskotDesa

Database Migration:

✅ COMPLETED: bunx prisma db push
✅ Prisma Client regenerated successfully

2. Hardcoded Nama Perbekel di UI FIXED

File: src/app/admin/(dashboard)/desa/profil/profil-perbekel/page.tsx

Changes:

// BEFORE (Line 95-102)
<Text>I.B. Surya Prabhawa Manuaba, S.H., M.H.</Text>

// AFTER
<Text>{perbekel.nama || "I.B. Surya Prabhawa Manuaba, S.H., M.H."}</Text>

Impact:

  • Nama perbekel sekarang dinamis dari database
  • Fallback ke nama lama jika data kosong (backward compatible)

3. Magic String "edit" - Created /first Endpoint FIXED

New Files Created:

  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/sejarah/find-first.ts
  • Updated src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/sejarah/index.ts

New Endpoint:

GET /api/desa/profile/sejarah/first

Features:

  • Authentication required (menggunakan requireAuth)
  • Returns first active record (orderBy createdAt asc)
  • No more magic string "edit"
  • Type-safe dan scalable

Usage:

// OLD (magic string)
stateProfileDesa.sejarahDesa.findUnique.load("edit");

// NEW (type-safe)
const response = await ApiFetch.api.desa.profile.sejarah.first.get();

4. Authentication Helper Libraries CREATED

New Files:

  • src/lib/api-auth.ts - Authentication helper dengan requireAuth dan optionalAuth
  • src/lib/session.ts - Session helper menggunakan iron-session

Features:

  • Session-based authentication
  • Auto-redirect jika tidak authenticated
  • Check user isActive status
  • Error handling lengkap

Usage Example:

import { requireAuth } from "@/lib/api-auth";

export default async function myEndpoint(context: Context) {
  const authResult = await requireAuth(context);
  if (!authResult.authenticated) {
    return authResult.response; // 401 Unauthorized
  }
  
  // Lanjut proses dengan authResult.user
  console.log("User:", authResult.user);
}

5. Authentication Added to Update Endpoint FIXED

File: src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/sejarah/update.ts

Changes:

// BEFORE
import prisma from "@/lib/prisma";
import { Context } from "elysia";

export default async function sejarahDesaUpdate(context: Context) {
  // ❌ No authentication
  const id = context.params?.id as string;
  // ...
}

// AFTER
import prisma from "@/lib/prisma";
import { requireAuth } from "@/lib/api-auth";
import { Context } from "elysia";

export default async function sejarahDesaUpdate(context: Context) {
  // ✅ Authentication check
  const authResult = await requireAuth(context);
  if (!authResult.authenticated) {
    return authResult.response;
  }
  
  const id = context.params?.id as string;
  // ...
}

⚠️ REMAINING FIXES (Manual Required)

1. Add Authentication to ALL Profile API Endpoints

Files that need authentication:

Profile Desa (Sejarah, Visi Misi, Lambang, Maskot):

  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/sejarah/find-by-id.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/visi-misi/find-by-id.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/visi-misi/update.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/lambang-desa/find-by-id.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/lambang-desa/update.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/maskot-desa/find-by-id.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/maskot-desa/update.ts

Profile Perbekel:

  • src/app/api/[[...slugs]]/_lib/desa/profile/profilePerbekel/find-by-id.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profilePerbekel/update.ts

Profile Mantan Perbekel:

  • src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/create.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findMany.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/findUnique.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/updt.ts
  • src/app/api/[[...slugs]]/_lib/desa/profile/profile-mantan-perbekel/del.ts

How to Add Authentication:

// Tambahkan di awal function (sebelum logic utama)
import { requireAuth } from "@/lib/api-auth";

export default async function myEndpoint(context: Context) {
  // ✅ Authentication check
  const authResult = await requireAuth(context);
  if (!authResult.authenticated) {
    return authResult.response;
  }
  
  // ... existing code
}

2. Fix Maskot Image Delete Logic

File: src/app/api/[[...slugs]]/_lib/desa/profile/profile_desa/maskot-desa/update.ts

Current Bug:

// ❌ Menghapus SEMUA gambar lama
for (const old of existing.images) {
  await prisma.fileStorage.delete({ where: { id: old.imageId } });
}

Fix Required:

// ✅ Implementasi diff logic
const oldImageIds = existing.images.map(img => img.imageId);
const newImageIds = body.images?.filter(img => img.imageId).map(img => img.imageId) || [];

// Find images to delete (in old but not in new)
const imagesToDelete = oldImageIds.filter(id => !newImageIds.includes(id));

// Delete only removed images
for (const imageId of imagesToDelete) {
  if (imageId) {
    const oldImage = await prisma.fileStorage.findUnique({ where: { id: imageId } });
    if (oldImage) {
      try {
        const filePath = path.join(oldImage.path, oldImage.name);
        await fs.unlink(filePath);
        await prisma.fileStorage.delete({ where: { id: imageId } });
      } catch (error) {
        console.error('Failed to delete old image:', error);
      }
    }
  }
}

3. Update State Management to Use /first Endpoint

File: src/app/admin/(dashboard)/_state/desa/profile.ts

Current Code (Line ~36):

// ❌ Magic string "edit"
async load(id: string) {
  const response = await fetch(`/api/desa/profile/sejarah/${id}`);
  // ...
}

// Usage di page:
stateProfileDesa.sejarahDesa.findUnique.load("edit");

Fix Required:

// ✅ Gunakan /first endpoint
async loadFirst() {
  this.loading = true;
  this.error = null;
  
  try {
    const response = await ApiFetch.api.desa.profile.sejarah.first.get();
    
    if (response.success) {
      this.data = response.data;
      return response.data;
    } else {
      throw new Error(response.message || "Gagal mengambil data");
    }
  } catch (error) {
    const msg = (error as Error).message;
    this.error = msg;
    console.error("Load sejarah desa error:", msg);
    toast.error("Terjadi kesalahan");
    return null;
  } finally {
    this.loading = false;
  }
}

// Usage di page:
stateProfileDesa.sejarahDesa.findUnique.loadFirst();

4. Add XSS Sanitization

Files that use dangerouslySetInnerHTML:

  • src/app/admin/(dashboard)/desa/profil/profil-perbekel/page.tsx (multiple places)
  • src/app/admin/(dashboard)/desa/profil/profil-perbekel/[id]/page.tsx

Fix Required:

// Install: bun add dompurify
import DOMPurify from 'dompurify';

// Usage
<div
  dangerouslySetInnerHTML={{
    __html: DOMPurify.sanitize(perbekel.biodata, {
      ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'ul', 'ol', 'li'],
      ALLOWED_ATTR: []
    })
  }}
/>

📋 TESTING CHECKLIST

Database Changes:

  • Verify schema changes applied: bunx prisma db push
  • Check Prisma Client regenerated
  • Test create new data (should not auto-delete)

API Authentication:

  • Test endpoint tanpa login (should return 401)
  • Test endpoint dengan login (should work)
  • Test dengan user inactive (should return 403)

/first Endpoint:

  • Test GET /api/desa/profile/sejarah/first
  • Verify returns first active record
  • Test tanpa authentication (should fail)

UI Changes:

  • Check perbekel name dynamic (not hardcoded)
  • Test with different perbekel data
  • Verify fallback to old name if data empty

🚀 NEXT STEPS

  1. Add authentication ke semua API endpoints (15 files)
  2. Fix maskot image delete logic (1 file)
  3. Update state management untuk gunakan /first endpoint
  4. Add XSS sanitization di semua page yang pakai dangerouslySetInnerHTML
  5. Test semua changes secara thorough

📝 NOTES

  • Schema fix sudah di-push ke database
  • Authentication helper sudah dibuat dan bisa di-reuse
  • /first endpoint sudah dibuat sebagai contoh
  • ⚠️ Remaining fixes butuh manual update karena banyak file

Estimated Time to Complete:

  • Add auth to all endpoints: ~2-3 jam
  • Fix maskot delete logic: ~30 menit
  • Update state management: ~1 jam
  • Add XSS sanitization: ~30 menit
  • Testing: ~1-2 jam

Total: ~5-6 jam


Last Updated: 25 Februari 2026
Status: 3/5 Critical Issues Fixed (60% Complete)