Fix Eror Grafik Realisasi-2
This commit is contained in:
73
AUDIT_REPORT.md
Normal file
73
AUDIT_REPORT.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Engineering Audit Report: Desa Darmasaba
|
||||||
|
**Status:** Production Readiness Review (Critical)
|
||||||
|
**Auditor:** Staff Technical Architect
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Executive Summary & Scores
|
||||||
|
|
||||||
|
| Category | Score | Status |
|
||||||
|
| :--- | :---: | :--- |
|
||||||
|
| **Project Architecture** | 3/10 | 🔴 Critical Failure |
|
||||||
|
| **Code Quality** | 4/10 | 🟠 Poor |
|
||||||
|
| **Performance** | 5/10 | 🟡 Mediocre |
|
||||||
|
| **Security** | 5/10 | 🟠 Risk Detected |
|
||||||
|
| **Production Readiness** | 2/10 | 🔴 Not Ready |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ 1. Project Architecture
|
||||||
|
The project suffers from a **"Frankenstein Architecture"**. It attempts to run a full Elysia.js instance inside a Next.js Catch-All route.
|
||||||
|
- **Fractured Backend:** Logic is split between standard Next.js routes (`/api/auth`) and embedded Elysia modules.
|
||||||
|
- **Stateful Dependency:** Reliance on local filesystem (`WIBU_UPLOAD_DIR`) makes the application impossible to deploy on modern serverless platforms like Vercel.
|
||||||
|
- **Polluted Namespace:** Routing tree contains "test/coba" folders (`src/app/coba`, `src/app/percobaan`) that would be accessible in production.
|
||||||
|
|
||||||
|
## ⚛️ 2. Frontend Engineering (React / Next.js)
|
||||||
|
- **State Management Chaos:** Simultaneous use of `Valtio`, `Jotai`, `React Context`, and `localStorage`.
|
||||||
|
- **Tight Coupling:** Public pages (`/darmasaba`) import state directly from Admin internal states (`/admin/(dashboard)/_state`).
|
||||||
|
- **Heavy Client-Side Logic:** Logic that belongs in Server Actions or Hooks is embedded in presentational components (e.g., `Footer.tsx`).
|
||||||
|
|
||||||
|
## 📡 3. Backend / API Design
|
||||||
|
- **Framework Overhead:** Running Elysia inside Next.js adds unnecessary cold-boot overhead and complexity.
|
||||||
|
- **Weak Validation:** Widespread use of `as Type` casting in API handlers instead of runtime validation (Zod/Schema).
|
||||||
|
- **Service Integration:** OTP codes are sent via external `GET` requests with sensitive data in the query string—a major logging risk.
|
||||||
|
|
||||||
|
## 🗄️ 4. Database & Data Modeling (Prisma)
|
||||||
|
- **Schema Over-Normalization:** ~2000 lines of schema. Every minor content type (e.g., `LambangDesa`) is a separate table instead of a unified CMS model.
|
||||||
|
- **Polymorphic Monolith:** `FileStorage` is a "god table" with optional relations to ~40 other tables, creating a massive bottleneck and data integrity risk.
|
||||||
|
- **Connection Mismanagement:** Manual `prisma.$disconnect()` in API routes kills connection pooling performance.
|
||||||
|
|
||||||
|
## 🚀 5. Performance Engineering
|
||||||
|
- **Bypassing Optimization:** Custom `/api/utils/img` endpoint bypasses `next/image` optimization, serving uncompressed assets.
|
||||||
|
- **Aggressive Polling:** Client-side 30s polling for notifications is battery-draining and inefficient compared to SSE or SWR.
|
||||||
|
|
||||||
|
## 🔒 6. Security Audit
|
||||||
|
- **Insecure OTP Delivery:** Credentials passed as URL parameters to the WhatsApp service.
|
||||||
|
- **File Upload Risks:** Potential for Arbitrary File Upload due to direct local filesystem writes without rigorous sanitization.
|
||||||
|
|
||||||
|
## 🧹 7. Code Quality
|
||||||
|
- **Inconsistency:** Mixed English/Indonesian naming (e.g., `nomor` vs `createdAt`).
|
||||||
|
- **Artifacts:** Root directory is littered with scratch files: `xcoba.ts`, `xx.ts`, `test.txt`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚩 Top 10 Critical Problems
|
||||||
|
1. **Architectural Fracture:** Embedding Elysia inside Next.js creates a "split-brain" system.
|
||||||
|
2. **Serverless Incompatibility:** Dependency on local disk storage for uploads.
|
||||||
|
3. **Database Bloat:** Over-complicated schema with a fragile `FileStorage` monolith.
|
||||||
|
4. **State Fragmentation:** Mixed usage of Jotai and Valtio without a clear standard.
|
||||||
|
5. **Credential Leakage:** OTP codes sent via GET query parameters.
|
||||||
|
6. **Poor Cleanup:** Trial/Test folders and files committed to the production source.
|
||||||
|
7. **Asset Performance:** Bypassing Next.js image optimization.
|
||||||
|
8. **Coupling:** High dependency between public UI and internal Admin state.
|
||||||
|
9. **Type Safety:** Manual casting in APIs instead of runtime validation.
|
||||||
|
10. **Connection Pooling:** Inefficient Prisma connection management.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Tech Lead Refactoring Priorities
|
||||||
|
1. **Unify the API:** Decommission the Elysia wrapper. Port all logic to standard Next.js Route Handlers with Zod validation.
|
||||||
|
2. **Stateless Storage:** Implement an S3-compatible adapter for all file uploads. Remove `fs` usage.
|
||||||
|
3. **Schema Consolidation:** Refactor the schema to use generic content models where possible.
|
||||||
|
4. **Standardize State:** Choose one global state manager and migrate all components.
|
||||||
|
5. **Project Sanitization:** Delete all `coba`, `percobaan`, and scratch files (`xcoba.ts`, etc.).
|
||||||
@@ -1,11 +1,31 @@
|
|||||||
import { Paper, Title, Progress, Stack, Text, Group, Box } from '@mantine/core';
|
import { Paper, Title, Progress, Stack, Text, Group, Box } from '@mantine/core';
|
||||||
|
|
||||||
function Summary({ title, data }: any) {
|
interface APBDesItem {
|
||||||
|
tipe: string;
|
||||||
|
anggaran: number;
|
||||||
|
realisasi?: number;
|
||||||
|
totalRealisasi?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface APBDesData {
|
||||||
|
tahun?: number;
|
||||||
|
items?: APBDesItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SummaryProps {
|
||||||
|
title: string;
|
||||||
|
data: APBDesItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function Summary({ title, data }: SummaryProps) {
|
||||||
if (!data || data.length === 0) return null;
|
if (!data || data.length === 0) return null;
|
||||||
|
|
||||||
const totalAnggaran = data.reduce((s: number, i: any) => s + i.anggaran, 0);
|
const totalAnggaran = data.reduce((s: number, i: APBDesItem) => s + i.anggaran, 0);
|
||||||
// Use realisasi field (already mapped from totalRealisasi in transformAPBDesData)
|
// Use realisasi field (already mapped from totalRealisasi in transformAPBDesData)
|
||||||
const totalRealisasi = data.reduce((s: number, i: any) => s + (i.realisasi || i.totalRealisasi || 0), 0);
|
const totalRealisasi = data.reduce(
|
||||||
|
(s: number, i: APBDesItem) => s + (i.realisasi || i.totalRealisasi || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
const persen =
|
const persen =
|
||||||
totalAnggaran > 0 ? (totalRealisasi / totalAnggaran) * 100 : 0;
|
totalAnggaran > 0 ? (totalRealisasi / totalAnggaran) * 100 : 0;
|
||||||
@@ -77,28 +97,13 @@ function Summary({ title, data }: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GrafikRealisasi({ apbdesData }: any) {
|
export default function GrafikRealisasi({ apbdesData }: { apbdesData: APBDesData }) {
|
||||||
const items = apbdesData.items || [];
|
const items = apbdesData.items || [];
|
||||||
const tahun = apbdesData.tahun || new Date().getFullYear();
|
const tahun = apbdesData.tahun || new Date().getFullYear();
|
||||||
|
|
||||||
const pendapatan = items.filter((i: any) => i.tipe === 'pendapatan');
|
const pendapatan = items.filter((i: APBDesItem) => i.tipe === 'pendapatan');
|
||||||
const belanja = items.filter((i: any) => i.tipe === 'belanja');
|
const belanja = items.filter((i: APBDesItem) => i.tipe === 'belanja');
|
||||||
const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan');
|
const pembiayaan = items.filter((i: APBDesItem) => i.tipe === 'pembiayaan');
|
||||||
|
|
||||||
// // Hitung total keseluruhan
|
|
||||||
// const totalAnggaranSemua = items.reduce((s: number, i: any) => s + i.anggaran, 0);
|
|
||||||
// // Use realisasi field (already mapped from totalRealisasi in transformAPBDesData)
|
|
||||||
// const totalRealisasiSemua = items.reduce((s: number, i: any) => s + (i.realisasi || i.totalRealisasi || 0), 0);
|
|
||||||
// const persenSemua = totalAnggaranSemua > 0 ? (totalRealisasiSemua / totalAnggaranSemua) * 100 : 0;
|
|
||||||
|
|
||||||
// const formatRupiah = (angka: number) => {
|
|
||||||
// return new Intl.NumberFormat('id-ID', {
|
|
||||||
// style: 'currency',
|
|
||||||
// currency: 'IDR',
|
|
||||||
// minimumFractionDigits: 0,
|
|
||||||
// maximumFractionDigits: 0,
|
|
||||||
// }).format(angka);
|
|
||||||
// };
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper withBorder p="md" radius="md">
|
<Paper withBorder p="md" radius="md">
|
||||||
@@ -111,27 +116,6 @@ export default function GrafikRealisasi({ apbdesData }: any) {
|
|||||||
<Summary title="💸 Belanja" data={belanja} />
|
<Summary title="💸 Belanja" data={belanja} />
|
||||||
<Summary title="📊 Pembiayaan" data={pembiayaan} />
|
<Summary title="📊 Pembiayaan" data={pembiayaan} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Summary Total Keseluruhan
|
|
||||||
<Box p="md" bg="gray.0">
|
|
||||||
<>
|
|
||||||
<Group justify="space-between" mb="xs">
|
|
||||||
<Text fw={700} fz="lg">TOTAL KESELURUHAN</Text>
|
|
||||||
<Text fw={700} fz="xl" c={persenSemua >= 100 ? 'teal' : persenSemua >= 80 ? 'blue' : 'red'}>
|
|
||||||
{persenSemua.toFixed(2)}%
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
<Text fz="sm" c="dimmed" mb="xs">
|
|
||||||
{formatRupiah(totalRealisasiSemua)} / {formatRupiah(totalAnggaranSemua)}
|
|
||||||
</Text>
|
|
||||||
<Progress
|
|
||||||
value={persenSemua}
|
|
||||||
size="lg"
|
|
||||||
radius="xl"
|
|
||||||
color={persenSemua >= 100 ? 'teal' : persenSemua >= 80 ? 'blue' : 'red'}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
</Box> */}
|
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user