Compare commits
153 Commits
test-model
...
deploy/stg
| Author | SHA1 | Date | |
|---|---|---|---|
| 09c7fd8f3a | |||
| 656ffcc561 | |||
| 76ffa662c5 | |||
| 46423409fd | |||
| 2edf5e9b11 | |||
| af368eeee0 | |||
| e104cd8fcc | |||
| 50801e5c8a | |||
| 80186bf493 | |||
| d84edc44f5 | |||
| 8b14c6ce44 | |||
| 5e822f0b05 | |||
| 34a37dc63b | |||
| 0e6f7e1769 | |||
| feb853d06e | |||
| 3de412afe0 | |||
| 87d234e57f | |||
| fd18a22834 | |||
| 3e8b961e52 | |||
| 82d779e5e0 | |||
| a6517166cb | |||
| 483b6be677 | |||
| f8dad0dbcd | |||
| 74301fe074 | |||
| 8b19abc628 | |||
| e73798a0f2 | |||
| 1a91f3c9ad | |||
| 9b74592101 | |||
| 55f4b94082 | |||
| b403bc754c | |||
| 67b87f145e | |||
| dd09d7c90a | |||
| 59ae8ad039 | |||
| c012d5778c | |||
| af31bd8aef | |||
| 721357adcf | |||
| 39776ec355 | |||
| 50a7356618 | |||
| 4494dd98ef | |||
| 970949a68b | |||
| 8777c45a44 | |||
| 42bcba6c96 | |||
| d1d54e5c25 | |||
| 0a4b85fd82 | |||
| b751f031cd | |||
| a3940321a7 | |||
| 3cd6fcbd81 | |||
| 7d9b7b0c60 | |||
| 0806eb2308 | |||
|
|
6064ef0759 | ||
| 1c00c326c9 | |||
| 51ce823b45 | |||
| 4eba96140d | |||
| f6f0e10935 | |||
| 2108f403aa | |||
| 4dfcf20322 | |||
| c6c3eebadf | |||
|
|
6d26ace8ab | ||
|
|
0dabc204bc | ||
|
|
e8f8b51686 | ||
|
|
a4db3a149d | ||
|
|
fece983ac5 | ||
| 8b7eef5fee | |||
| 8b22d01e0d | |||
| dc13e37a02 | |||
| 2d2cbef29b | |||
| 8c8a96b830 | |||
| dc3eccacbf | |||
| ffe94992e5 | |||
|
|
f5566bca2c | ||
|
|
ba964df32c | ||
|
|
df3f382a97 | ||
| 4fb522f88f | |||
| 85332a8225 | |||
| 3fe2a5ccab | |||
| 363bfa65fb | |||
| dccf590cbf | |||
| f076b81d14 | |||
| b5ea3216e0 | |||
| 64b116588b | |||
| 63161e1a39 | |||
| 8b8c65dd1e | |||
| 159fb3cec6 | |||
| 4821934224 | |||
| ee39b88b00 | |||
| ce46d3b5f7 | |||
| 144ac37e12 | |||
| f90477ed63 | |||
| 4a7811e06f | |||
| f63aaf916d | |||
| 3803c79c95 | |||
| 2d901912ea | |||
| a791efe76c | |||
| e9f7bc2043 | |||
|
|
7368a367f4 | ||
|
|
ed664d5b10 | ||
|
|
0ba30aa5b2 | ||
|
|
790d6535e5 | ||
|
|
46ce16ae97 | ||
| 0160fa636d | |||
| 3684e83187 | |||
| 77c54b5c8a | |||
| bb80b0ecc1 | |||
| 1b59d6bf09 | |||
| eb1ad54db6 | |||
| 21ec3ad1c1 | |||
| 3a115908c4 | |||
| 5ff791642c | |||
| b803c7a90c | |||
| fb2fe67c23 | |||
| 51460558d4 | |||
| d105ceeb6b | |||
| c865aee766 | |||
| 273dfdfd09 | |||
| 1d1d8e50dc | |||
| 092afe67d2 | |||
| 2d9170705d | |||
| fdf9a951a4 | |||
| ca74029688 | |||
| 1a8fc1a670 | |||
| 19235f0791 | |||
| 61de7d8d33 | |||
| 8fb85ce56c | |||
| 1f98b6993d | |||
| f3a10d63d1 | |||
| 7a42bec63b | |||
| 44c421129e | |||
| ddff427926 | |||
| 00c8caade4 | |||
| 0209f49449 | |||
| 344c6ada6d | |||
| 11acd04419 | |||
| 8d49213b68 | |||
| 96911e3cf1 | |||
| 9950c28b9b | |||
| fa0f3538d1 | |||
| 2778f53aff | |||
| 37ac91d4f4 | |||
| 217f4a9a3b | |||
| 5d6a7437ed | |||
| 752a6cabee | |||
| 134ddc6154 | |||
| 28979c6b49 | |||
| b2066caa13 | |||
| 023c77d636 | |||
| 9bf3ec72cf | |||
| f359f5b1ce | |||
| 1c1e8fb190 | |||
| 54f83da3b8 | |||
| f8985c550f | |||
| e3d909e760 | |||
| 16a8df50c1 | |||
| d66a952d4c |
47
.dockerignore
Normal file
47
.dockerignore
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
bun-debug.log*
|
||||||
|
|
||||||
|
# Docker files
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Markdown/Documentation
|
||||||
|
README.md
|
||||||
|
GEMINI.md
|
||||||
|
AGENTS.md
|
||||||
|
AUDIT_REPORT.md
|
||||||
|
QWEN.md
|
||||||
|
NOTE.md
|
||||||
|
task-project-apbdes.md
|
||||||
|
MUSIK_CREATE_ANALYSIS.md
|
||||||
|
darkMode.md
|
||||||
|
/test-results
|
||||||
|
/playwright-report
|
||||||
|
/tmp_assets
|
||||||
|
/foldergambar
|
||||||
|
/googleapi
|
||||||
|
/xx
|
||||||
|
/xx.ts
|
||||||
|
/xx.txt
|
||||||
|
/test.txt
|
||||||
|
/x.json
|
||||||
|
/x.sh
|
||||||
|
/xcoba.ts
|
||||||
|
/xcoba2.ts
|
||||||
|
/gambar.ttx
|
||||||
|
/test-berita-state.ts
|
||||||
44
.env.example
Normal file
44
.env.example
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Database Configuration
|
||||||
|
DATABASE_URL="postgresql://username:password@localhost:5432/desa-darmasaba?schema=public"
|
||||||
|
|
||||||
|
# Seafile Configuration (File Storage)
|
||||||
|
SEAFILE_TOKEN=your_seafile_token
|
||||||
|
SEAFILE_REPO_ID=your_seafile_repo_id
|
||||||
|
SEAFILE_URL=https://your-seafile-instance.com
|
||||||
|
SEAFILE_PUBLIC_SHARE_TOKEN=your_seafile_public_share_token
|
||||||
|
|
||||||
|
# Upload Configuration
|
||||||
|
WIBU_UPLOAD_DIR=uploads
|
||||||
|
WIBU_DOWNLOAD_DIR=./download
|
||||||
|
|
||||||
|
# WhatsApp Server Configuration
|
||||||
|
WA_SERVER_TOKEN=your_whatsapp_server_token
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
# IMPORTANT: For staging/production, set this to your actual domain
|
||||||
|
# Local development: NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||||
|
# Staging: NEXT_PUBLIC_BASE_URL=https://desa-darmasaba-stg.wibudev.com
|
||||||
|
# Production: NEXT_PUBLIC_BASE_URL=https://your-production-domain.com
|
||||||
|
# Or use relative URL '/' for automatic protocol/domain detection (recommended)
|
||||||
|
NEXT_PUBLIC_BASE_URL=/
|
||||||
|
|
||||||
|
# Email Configuration (for notifications/subscriptions)
|
||||||
|
EMAIL_USER=your_email@gmail.com
|
||||||
|
EMAIL_PASS=your_email_app_password
|
||||||
|
|
||||||
|
# Session Configuration
|
||||||
|
BASE_SESSION_KEY=your_session_key_generate_secure_random_string
|
||||||
|
BASE_TOKEN_KEY=your_jwt_secret_key_generate_secure_random_string
|
||||||
|
|
||||||
|
# Telegram Bot Configuration (for notifications)
|
||||||
|
BOT_TOKEN=your_telegram_bot_token
|
||||||
|
CHAT_ID=your_telegram_chat_id
|
||||||
|
|
||||||
|
# Session Password (for iron-session)
|
||||||
|
SESSION_PASSWORD="your_session_password_min_32_characters_long_secure"
|
||||||
|
|
||||||
|
# ElevenLabs API Key (for TTS features - optional)
|
||||||
|
ELEVENLABS_API_KEY=your_elevenlabs_api_key
|
||||||
|
|
||||||
|
# Environment (optional, defaults to development)
|
||||||
|
# NODE_ENV=development
|
||||||
@@ -1,43 +1,52 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync, existsSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
// Fungsi untuk mencari string terpanjang dalam objek (biasanya balasan AI)
|
// Function to manually load .env from project root if process.env is missing keys
|
||||||
function findLongestString(obj: any): string {
|
function loadEnv() {
|
||||||
let longest = "";
|
const envPath = join(process.cwd(), ".env");
|
||||||
const search = (item: any) => {
|
if (existsSync(envPath)) {
|
||||||
if (typeof item === "string") {
|
const envContent = readFileSync(envPath, "utf-8");
|
||||||
if (item.length > longest.length) longest = item;
|
const lines = envContent.split("\n");
|
||||||
} else if (Array.isArray(item)) {
|
for (const line of lines) {
|
||||||
item.forEach(search);
|
if (line && !line.startsWith("#")) {
|
||||||
} else if (item && typeof item === "object") {
|
const [key, ...valueParts] = line.split("=");
|
||||||
Object.values(item).forEach(search);
|
if (key && valueParts.length > 0) {
|
||||||
|
const value = valueParts.join("=").trim().replace(/^["']|["']$/g, "");
|
||||||
|
process.env[key.trim()] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
search(obj);
|
|
||||||
return longest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
|
// Ensure environment variables are loaded
|
||||||
|
loadEnv();
|
||||||
|
|
||||||
const inputRaw = readFileSync(0, "utf-8");
|
const inputRaw = readFileSync(0, "utf-8");
|
||||||
if (!inputRaw) return;
|
if (!inputRaw) return;
|
||||||
const input = JSON.parse(inputRaw);
|
|
||||||
|
|
||||||
// DEBUG: Lihat struktur asli di console terminal (stderr)
|
let finalText = "";
|
||||||
console.error("DEBUG KEYS:", Object.keys(input));
|
let sessionId = "web-desa-darmasaba";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try parsing as JSON first
|
||||||
|
const input = JSON.parse(inputRaw);
|
||||||
|
sessionId = input.session_id || "web-desa-darmasaba";
|
||||||
|
finalText = typeof input === "string" ? input : (input.response || input.text || JSON.stringify(input));
|
||||||
|
} catch {
|
||||||
|
// If not JSON, use raw text
|
||||||
|
finalText = inputRaw;
|
||||||
|
}
|
||||||
|
|
||||||
const BOT_TOKEN = process.env.BOT_TOKEN;
|
const BOT_TOKEN = process.env.BOT_TOKEN;
|
||||||
const CHAT_ID = process.env.CHAT_ID;
|
const CHAT_ID = process.env.CHAT_ID;
|
||||||
|
|
||||||
const sessionId = input.session_id || "unknown";
|
if (!BOT_TOKEN || !CHAT_ID) {
|
||||||
|
console.error("Missing BOT_TOKEN or CHAT_ID in environment variables");
|
||||||
// Cari teks secara otomatis di seluruh objek JSON
|
return;
|
||||||
let finalText = findLongestString(input.response || input);
|
|
||||||
|
|
||||||
if (!finalText || finalText.length < 5) {
|
|
||||||
finalText =
|
|
||||||
"Teks masih gagal diekstraksi. Struktur: " +
|
|
||||||
Object.keys(input).join(", ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const message =
|
const message =
|
||||||
@@ -45,7 +54,7 @@ async function run() {
|
|||||||
`🆔 Session: \`${sessionId}\` \n\n` +
|
`🆔 Session: \`${sessionId}\` \n\n` +
|
||||||
`🧠 Output:\n${finalText.substring(0, 3500)}`;
|
`🧠 Output:\n${finalText.substring(0, 3500)}`;
|
||||||
|
|
||||||
await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
|
const res = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -55,6 +64,13 @@ async function run() {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorData = await res.json();
|
||||||
|
console.error("Telegram API Error:", errorData);
|
||||||
|
} else {
|
||||||
|
console.log("Notification sent successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
process.stdout.write(JSON.stringify({ status: "continue" }));
|
process.stdout.write(JSON.stringify({ status: "continue" }));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Hook Error:", err);
|
console.error("Hook Error:", err);
|
||||||
|
|||||||
219
.github/workflows/build.yml
vendored
219
.github/workflows/build.yml
vendored
@@ -1,219 +0,0 @@
|
|||||||
name: Build And Save Log
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
environment:
|
|
||||||
description: "Target environment (e.g., staging, production)"
|
|
||||||
required: true
|
|
||||||
default: "staging"
|
|
||||||
version:
|
|
||||||
description: "Version to deploy"
|
|
||||||
required: false
|
|
||||||
default: "latest"
|
|
||||||
|
|
||||||
env:
|
|
||||||
APP_NAME: desa-darmasaba-action
|
|
||||||
WA_PHONE: "6289697338821,6289697338822"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:14
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
|
||||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
||||||
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
|
||||||
ports:
|
|
||||||
- 5432:5432
|
|
||||||
options: >-
|
|
||||||
--health-cmd="pg_isready"
|
|
||||||
--health-interval=10s
|
|
||||||
--health-timeout=5s
|
|
||||||
--health-retries=5
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# Checkout kode sumber
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Setup Bun
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
|
|
||||||
# Cache dependencies
|
|
||||||
- name: Cache dependencies
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: .bun
|
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-bun-
|
|
||||||
|
|
||||||
# Step 1: Set BRANCH_NAME based on event type
|
|
||||||
- name: Set BRANCH_NAME
|
|
||||||
run: |
|
|
||||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
|
||||||
echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "BRANCH_NAME=${{ github.ref_name }}" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 2: Generate APP_VERSION dynamically
|
|
||||||
- name: Set APP_VERSION
|
|
||||||
run: echo "APP_VERSION=${{ github.sha }}---$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# Step 3: Kirim notifikasi ke API build Start
|
|
||||||
- name: Notify start build
|
|
||||||
run: |
|
|
||||||
IFS=',' read -ra PHONES <<< "${{ env.WA_PHONE }}"
|
|
||||||
for PHONE in "${PHONES[@]}"; do
|
|
||||||
ENCODED_TEXT=$(bun -e "console.log(encodeURIComponent('Build:start\nApp:${{ env.APP_NAME }}\nBranch:${{ env.BRANCH_NAME }}\nVersion:${{ env.APP_VERSION }}'))")
|
|
||||||
curl -X GET "https://wa.wibudev.com/code?text=$ENCODED_TEXT&nom=$PHONE"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
- name: Install dependencies
|
|
||||||
run: bun install
|
|
||||||
|
|
||||||
# Konfigurasi environment variable untuk PostgreSQL dan variabel tambahan
|
|
||||||
- name: Set up environment variables
|
|
||||||
run: |
|
|
||||||
echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}?schema=public" >> .env
|
|
||||||
echo "PORT=3000" >> .env
|
|
||||||
echo "NEXT_PUBLIC_WIBU_URL=localhost:3000" >> .env
|
|
||||||
echo "WIBU_UPLOAD_DIR=/uploads" >> .env
|
|
||||||
|
|
||||||
# Create log file
|
|
||||||
- name: Create log file
|
|
||||||
run: touch build.txt
|
|
||||||
|
|
||||||
# Migrasi database menggunakan Prisma
|
|
||||||
- name: Apply Prisma schema to database
|
|
||||||
run: bun prisma db push >> build.txt 2>&1
|
|
||||||
|
|
||||||
# Seed database (opsional)
|
|
||||||
- name: Seed database
|
|
||||||
run: |
|
|
||||||
bun prisma db seed >> build.txt 2>&1 || echo "Seed failed or no seed data found. Continuing without seed." >> build.txt
|
|
||||||
|
|
||||||
# Build project
|
|
||||||
- name: Build project
|
|
||||||
run: bun run build >> build.txt 2>&1
|
|
||||||
|
|
||||||
# Ensure project directory exists
|
|
||||||
- name: Ensure /var/www/projects/${{ env.APP_NAME }} exists
|
|
||||||
uses: appleboy/ssh-action@master
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.VPS_HOST }}
|
|
||||||
username: ${{ secrets.VPS_USERNAME }}
|
|
||||||
key: ${{ secrets.VPS_SSH_KEY }}
|
|
||||||
script: |
|
|
||||||
mkdir -p /var/www/projects/${{ env.APP_NAME }}
|
|
||||||
|
|
||||||
# Deploy to a new version directory
|
|
||||||
- name: Deploy to VPS (New Version)
|
|
||||||
uses: appleboy/scp-action@master
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.VPS_HOST }}
|
|
||||||
username: ${{ secrets.VPS_USERNAME }}
|
|
||||||
key: ${{ secrets.VPS_SSH_KEY }}
|
|
||||||
source: "."
|
|
||||||
target: "/var/www/projects/${{ env.APP_NAME }}/releases/${{ env.APP_VERSION }}"
|
|
||||||
|
|
||||||
# Set up environment variables
|
|
||||||
- name: Set up environment variables
|
|
||||||
run: |
|
|
||||||
rm -r .env
|
|
||||||
echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5433/${{ secrets.POSTGRES_DB }}?schema=public" >> .env
|
|
||||||
echo "NEXT_PUBLIC_WIBU_URL=${{ env.APP_NAME }}" >> .env
|
|
||||||
echo "WIBU_UPLOAD_DIR=/var/www/projects/${{ env.APP_NAME }}/uploads" >> .env
|
|
||||||
|
|
||||||
# Kirim file .env ke server
|
|
||||||
- name: Upload .env to server
|
|
||||||
uses: appleboy/scp-action@master
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.VPS_HOST }}
|
|
||||||
username: ${{ secrets.VPS_USERNAME }}
|
|
||||||
key: ${{ secrets.VPS_SSH_KEY }}
|
|
||||||
source: ".env"
|
|
||||||
target: "/var/www/projects/${{ env.APP_NAME }}/releases/${{ env.APP_VERSION }}/"
|
|
||||||
|
|
||||||
# manage deployment
|
|
||||||
- name: manage deployment
|
|
||||||
uses: appleboy/ssh-action@master
|
|
||||||
with:
|
|
||||||
host: ${{ secrets.VPS_HOST }}
|
|
||||||
username: ${{ secrets.VPS_USERNAME }}
|
|
||||||
key: ${{ secrets.VPS_SSH_KEY }}
|
|
||||||
script: |
|
|
||||||
|
|
||||||
# Source ~/.bashrc
|
|
||||||
source ~/.bashrc
|
|
||||||
|
|
||||||
# Find an available port
|
|
||||||
PORT=$(curl -s -X GET https://wibu-bot.wibudev.com/api/find-port | jq -r '.[0]')
|
|
||||||
if [ -z "$PORT" ] || ! [[ "$PORT" =~ ^[0-9]+$ ]]; then
|
|
||||||
echo "Invalid or missing port from API."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# manage deployment
|
|
||||||
cd /var/www/projects/${{ env.APP_NAME }}/releases/${{ env.APP_VERSION }}
|
|
||||||
|
|
||||||
# Create uploads directory
|
|
||||||
mkdir -p /var/www/projects/${{ env.APP_NAME }}/uploads
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
bun install --production
|
|
||||||
|
|
||||||
# Apply database schema
|
|
||||||
if ! bun prisma db push; then
|
|
||||||
echo "Database migration failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Seed database (optional)
|
|
||||||
bun prisma db seed || echo "tidak membutuhkan seed"
|
|
||||||
|
|
||||||
# Restart the application
|
|
||||||
pm2 reload ${{ env.APP_NAME }} || pm2 start "bun run start --port $PORT" --name "${{ env.APP_NAME }}-$PORT" --namespace "${{ env.APP_NAME }}"
|
|
||||||
|
|
||||||
# Step 4: Set BUILD_STATUS based on success or failure
|
|
||||||
- name: Set BUILD_STATUS
|
|
||||||
if: success()
|
|
||||||
run: echo "BUILD_STATUS=success" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set BUILD_STATUS on failure
|
|
||||||
if: failure()
|
|
||||||
run: echo "BUILD_STATUS=failed" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# Update status log
|
|
||||||
- name: Update status log
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "=====================" >> build.txt
|
|
||||||
echo "BUILD_STATUS=${{ env.BUILD_STATUS }}" >> build.txt
|
|
||||||
echo "APP_NAME=${{ env.APP_NAME }}" >> build.txt
|
|
||||||
echo "APP_VERSION=${{ env.APP_VERSION }}" >> build.txt
|
|
||||||
echo "=====================" >> build.txt
|
|
||||||
|
|
||||||
# Upload log to 0x0.st
|
|
||||||
- name: Upload log to 0x0.st
|
|
||||||
id: upload_log
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
LOG_URL=$(curl -F "file=@build.txt" https://wibu-bot.wibudev.com/api/file )
|
|
||||||
echo "LOG_URL=$LOG_URL" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# Kirim notifikasi ke API
|
|
||||||
- name: Notify build success via API
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
IFS=',' read -ra PHONES <<< "${{ env.WA_PHONE }}"
|
|
||||||
for PHONE in "${PHONES[@]}"; do
|
|
||||||
ENCODED_TEXT=$(bun -e "console.log(encodeURIComponent('Build:${{ env.BUILD_STATUS }}\nApp:${{ env.APP_NAME }}\nBranch:${{ env.BRANCH_NAME }}\nVersion:${{ env.APP_VERSION }}\nLog:${{ env.LOG_URL }}'))")
|
|
||||||
curl -X GET "https://wa.wibudev.com/code?text=$ENCODED_TEXT&nom=$PHONE"
|
|
||||||
done
|
|
||||||
56
.github/workflows/docker-publish.yml
vendored
Normal file
56
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: Publish Docker to GHCR
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Build & Push to GHCR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Free disk space
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/share/dotnet
|
||||||
|
sudo rm -rf /usr/local/lib/android
|
||||||
|
sudo rm -rf /opt/ghc
|
||||||
|
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||||
|
sudo docker image prune --all --force
|
||||||
|
df -h
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Extract tag name
|
||||||
|
id: meta
|
||||||
|
run: echo "tag=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
106
.github/workflows/publish.yml
vendored
Normal file
106
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
name: Publish Docker to GHCR
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stack_env:
|
||||||
|
description: "stack env"
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
default: "dev"
|
||||||
|
options:
|
||||||
|
- dev
|
||||||
|
- prod
|
||||||
|
- stg
|
||||||
|
tag:
|
||||||
|
description: "Image tag (e.g. 1.0.0)"
|
||||||
|
required: true
|
||||||
|
default: "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Build & Push to GHCR ${{ github.repository }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: ${{ vars.PORTAINER_ENV || 'portainer' }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Free disk space
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/share/dotnet
|
||||||
|
sudo rm -rf /usr/local/lib/android
|
||||||
|
sudo rm -rf /opt/ghc
|
||||||
|
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||||
|
sudo docker image prune --all --force
|
||||||
|
df -h
|
||||||
|
|
||||||
|
- name: Checkout branch ${{ github.event.inputs.stack_env }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.stack_env }}
|
||||||
|
|
||||||
|
- name: Checkout scripts from main
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
path: .ci
|
||||||
|
sparse-checkout: .github/workflows/script
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate image metadata
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}
|
||||||
|
type=raw,value=${{ github.event.inputs.stack_env }}-latest
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
no-cache: true
|
||||||
|
|
||||||
|
- name: Notify success
|
||||||
|
if: success()
|
||||||
|
run: bash ./.ci/.github/workflows/script/notify.sh
|
||||||
|
env:
|
||||||
|
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
NOTIFY_STATUS: success
|
||||||
|
NOTIFY_WORKFLOW: "Publish Docker"
|
||||||
|
NOTIFY_DETAIL: "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}"
|
||||||
|
|
||||||
|
- name: Notify failure
|
||||||
|
if: failure()
|
||||||
|
run: bash ./.ci/.github/workflows/script/notify.sh
|
||||||
|
env:
|
||||||
|
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
NOTIFY_STATUS: failure
|
||||||
|
NOTIFY_WORKFLOW: "Publish Docker"
|
||||||
|
NOTIFY_DETAIL: "Image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.stack_env }}-${{ github.event.inputs.tag }}"
|
||||||
60
.github/workflows/re-pull.yml
vendored
Normal file
60
.github/workflows/re-pull.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: Re-Pull Docker
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stack_name:
|
||||||
|
description: "stack name"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
stack_env:
|
||||||
|
description: "stack env"
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
default: "dev"
|
||||||
|
options:
|
||||||
|
- dev
|
||||||
|
- stg
|
||||||
|
- prod
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Re-Pull Docker ${{ github.event.inputs.stack_name }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: ${{ vars.PORTAINER_ENV || 'portainer' }}
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout scripts from main
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
sparse-checkout: .github/workflows/script
|
||||||
|
|
||||||
|
- name: Deploy ke Portainer
|
||||||
|
run: bash ./.github/workflows/script/re-pull.sh
|
||||||
|
env:
|
||||||
|
PORTAINER_USERNAME: ${{ secrets.PORTAINER_USERNAME }}
|
||||||
|
PORTAINER_PASSWORD: ${{ secrets.PORTAINER_PASSWORD }}
|
||||||
|
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
|
||||||
|
STACK_NAME: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}
|
||||||
|
|
||||||
|
- name: Notify success
|
||||||
|
if: success()
|
||||||
|
run: bash ./.github/workflows/script/notify.sh
|
||||||
|
env:
|
||||||
|
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
NOTIFY_STATUS: success
|
||||||
|
NOTIFY_WORKFLOW: "Re-Pull Docker"
|
||||||
|
NOTIFY_DETAIL: "Stack: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}"
|
||||||
|
|
||||||
|
- name: Notify failure
|
||||||
|
if: failure()
|
||||||
|
run: bash ./.github/workflows/script/notify.sh
|
||||||
|
env:
|
||||||
|
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||||
|
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
NOTIFY_STATUS: failure
|
||||||
|
NOTIFY_WORKFLOW: "Re-Pull Docker"
|
||||||
|
NOTIFY_DETAIL: "Stack: ${{ github.event.inputs.stack_name }}-${{ github.event.inputs.stack_env }}"
|
||||||
26
.github/workflows/script/notify.sh
vendored
Normal file
26
.github/workflows/script/notify.sh
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
: "${TELEGRAM_TOKEN:?TELEGRAM_TOKEN tidak di-set}"
|
||||||
|
: "${TELEGRAM_CHAT_ID:?TELEGRAM_CHAT_ID tidak di-set}"
|
||||||
|
: "${NOTIFY_STATUS:?NOTIFY_STATUS tidak di-set}"
|
||||||
|
: "${NOTIFY_WORKFLOW:?NOTIFY_WORKFLOW tidak di-set}"
|
||||||
|
|
||||||
|
if [ "$NOTIFY_STATUS" = "success" ]; then
|
||||||
|
ICON="✅"
|
||||||
|
TEXT="${ICON} *${NOTIFY_WORKFLOW}* berhasil!"
|
||||||
|
else
|
||||||
|
ICON="❌"
|
||||||
|
TEXT="${ICON} *${NOTIFY_WORKFLOW}* gagal!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$NOTIFY_DETAIL" ]; then
|
||||||
|
TEXT="${TEXT}
|
||||||
|
${NOTIFY_DETAIL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(jq -n \
|
||||||
|
--arg chat_id "$TELEGRAM_CHAT_ID" \
|
||||||
|
--arg text "$TEXT" \
|
||||||
|
'{chat_id: $chat_id, text: $text, parse_mode: "Markdown"}')"
|
||||||
120
.github/workflows/script/re-pull.sh
vendored
Normal file
120
.github/workflows/script/re-pull.sh
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
: "${PORTAINER_URL:?PORTAINER_URL tidak di-set}"
|
||||||
|
: "${PORTAINER_USERNAME:?PORTAINER_USERNAME tidak di-set}"
|
||||||
|
: "${PORTAINER_PASSWORD:?PORTAINER_PASSWORD tidak di-set}"
|
||||||
|
: "${STACK_NAME:?STACK_NAME tidak di-set}"
|
||||||
|
|
||||||
|
# Timeout total: MAX_RETRY * SLEEP_INTERVAL detik
|
||||||
|
MAX_RETRY=60 # 60 × 10s = 10 menit
|
||||||
|
SLEEP_INTERVAL=10
|
||||||
|
|
||||||
|
echo "🔐 Autentikasi ke Portainer..."
|
||||||
|
TOKEN=$(curl -s -X POST "https://${PORTAINER_URL}/api/auth" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\": \"${PORTAINER_USERNAME}\", \"password\": \"${PORTAINER_PASSWORD}\"}" \
|
||||||
|
| jq -r .jwt)
|
||||||
|
|
||||||
|
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
|
||||||
|
echo "❌ Autentikasi gagal! Cek PORTAINER_URL, USERNAME, dan PASSWORD."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔍 Mencari stack: $STACK_NAME..."
|
||||||
|
STACK=$(curl -s -X GET "https://${PORTAINER_URL}/api/stacks" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
| jq ".[] | select(.Name == \"$STACK_NAME\")")
|
||||||
|
|
||||||
|
if [ -z "$STACK" ]; then
|
||||||
|
echo "❌ Stack '$STACK_NAME' tidak ditemukan di Portainer!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
STACK_ID=$(echo "$STACK" | jq -r .Id)
|
||||||
|
ENDPOINT_ID=$(echo "$STACK" | jq -r .EndpointId)
|
||||||
|
ENV=$(echo "$STACK" | jq '.Env // []')
|
||||||
|
|
||||||
|
# ── Catat container ID lama sebelum redeploy ──────────────────────────────────
|
||||||
|
echo "📸 Mencatat container aktif sebelum redeploy..."
|
||||||
|
CONTAINERS_BEFORE=$(curl -s -X GET \
|
||||||
|
"https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true&filters=%7B%22label%22%3A%5B%22com.docker.compose.project%3D${STACK_NAME}%22%5D%7D" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
|
|
||||||
|
OLD_IDS=$(echo "$CONTAINERS_BEFORE" | jq -r '[.[] | .Id] | join(",")')
|
||||||
|
echo " Container lama: $(echo "$CONTAINERS_BEFORE" | jq -r '[.[] | .Names[0]] | join(", ")')"
|
||||||
|
|
||||||
|
# ── Ambil compose file lalu trigger redeploy ─────────────────────────────────
|
||||||
|
echo "📄 Mengambil compose file..."
|
||||||
|
STACK_FILE=$(curl -s -X GET "https://${PORTAINER_URL}/api/stacks/${STACK_ID}/file" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
| jq -r .StackFileContent)
|
||||||
|
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg content "$STACK_FILE" \
|
||||||
|
--argjson env "$ENV" \
|
||||||
|
'{stackFileContent: $content, env: $env, pullImage: true}')
|
||||||
|
|
||||||
|
echo "🚀 Triggering redeploy $STACK_NAME (pull latest image)..."
|
||||||
|
HTTP_STATUS=$(curl -s -o /tmp/portainer_response.json -w "%{http_code}" \
|
||||||
|
-X PUT "https://${PORTAINER_URL}/api/stacks/${STACK_ID}?endpointId=${ENDPOINT_ID}" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD")
|
||||||
|
|
||||||
|
if [ "$HTTP_STATUS" != "200" ]; then
|
||||||
|
echo "❌ Redeploy gagal! HTTP Status: $HTTP_STATUS"
|
||||||
|
cat /tmp/portainer_response.json | jq .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "⏳ Menunggu image selesai di-pull dan container baru running..."
|
||||||
|
echo " (Timeout: $((MAX_RETRY * SLEEP_INTERVAL)) detik)"
|
||||||
|
|
||||||
|
COUNT=0
|
||||||
|
while [ $COUNT -lt $MAX_RETRY ]; do
|
||||||
|
sleep $SLEEP_INTERVAL
|
||||||
|
COUNT=$((COUNT + 1))
|
||||||
|
|
||||||
|
CONTAINERS=$(curl -s -X GET \
|
||||||
|
"https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true&filters=%7B%22label%22%3A%5B%22com.docker.compose.project%3D${STACK_NAME}%22%5D%7D" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}")
|
||||||
|
|
||||||
|
# Container baru = ID tidak ada di daftar container lama
|
||||||
|
NEW_RUNNING=$(echo "$CONTAINERS" | jq \
|
||||||
|
--arg old "$OLD_IDS" \
|
||||||
|
'[.[] | select(.State == "running" and ((.Id) as $id | ($old | split(",") | index($id)) == null))] | length')
|
||||||
|
|
||||||
|
FAILED=$(echo "$CONTAINERS" | jq \
|
||||||
|
'[.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not) and (.Names[0] | test("seed") | not))] | length')
|
||||||
|
|
||||||
|
echo "🔄 [$((COUNT * SLEEP_INTERVAL))s / $((MAX_RETRY * SLEEP_INTERVAL))s] Container baru running: ${NEW_RUNNING} | Gagal: ${FAILED}"
|
||||||
|
echo "$CONTAINERS" | jq -r '.[] | " → \(.Names[0]) | \(.State) | \(.Status) | id: \(.Id[:12])"'
|
||||||
|
|
||||||
|
if [ "$FAILED" -gt "0" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "❌ Ada container yang crash!"
|
||||||
|
echo "$CONTAINERS" | jq -r '.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not) and (.Names[0] | test("seed") | not)) | " → \(.Names[0]) | \(.Status)"'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NEW_RUNNING" -gt "0" ]; then
|
||||||
|
# Cleanup dangling images setelah redeploy sukses
|
||||||
|
echo "🧹 Membersihkan dangling images..."
|
||||||
|
curl -s -X POST "https://${PORTAINER_URL}/api/endpoints/${ENDPOINT_ID}/docker/images/prune" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"filters":{"dangling":["true"]}}' | jq -r '" Reclaimed: \(.SpaceReclaimed // 0 | . / 1073741824 | tostring | .[0:5]) GB"'
|
||||||
|
|
||||||
|
echo "✅ Cleanup selesai!"
|
||||||
|
echo ""
|
||||||
|
echo "✅ Stack $STACK_NAME berhasil di-redeploy dengan image baru dan running!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "❌ Timeout $((MAX_RETRY * SLEEP_INTERVAL))s! Container baru tidak kunjung running."
|
||||||
|
echo " Kemungkinan image masih dalam proses pull atau ada error di server."
|
||||||
|
exit 1
|
||||||
55
.github/workflows/test.yml
vendored
55
.github/workflows/test.yml
vendored
@@ -1,55 +0,0 @@
|
|||||||
name: test workflows
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
environment:
|
|
||||||
description: "Target environment (e.g., staging, production)"
|
|
||||||
required: true
|
|
||||||
default: "staging"
|
|
||||||
version:
|
|
||||||
description: "Version to deploy"
|
|
||||||
required: false
|
|
||||||
default: "latest"
|
|
||||||
|
|
||||||
env:
|
|
||||||
APP_NAME: desa-darmasaba-action
|
|
||||||
WA_PHONE: "6289697338821,6289697338822"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
# Checkout kode sumber
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Setup Bun
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
|
|
||||||
# Create log file
|
|
||||||
- name: Create log file
|
|
||||||
run: touch build.txt
|
|
||||||
|
|
||||||
# Step 1: Set BRANCH_NAME based on event type
|
|
||||||
- name: Set BRANCH_NAME
|
|
||||||
run: |
|
|
||||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
|
||||||
echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "BRANCH_NAME=${{ github.ref_name }}" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 2: Generate APP_VERSION dynamically
|
|
||||||
- name: Set APP_VERSION
|
|
||||||
run: echo "APP_VERSION=${{ github.sha }}---$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# Step 3: Kirim notifikasi ke API build Start
|
|
||||||
- name: Notify start build
|
|
||||||
run: |
|
|
||||||
IFS=',' read -ra PHONES <<< "${{ env.WA_PHONE }}"
|
|
||||||
for PHONE in "${PHONES[@]}"; do
|
|
||||||
ENCODED_TEXT=$(bun -e "console.log(encodeURIComponent('Build:start\nApp:${{ env.APP_NAME }}\nenv:${{ inputs.environment }}\nBranch:${{ env.BRANCH_NAME }}\nVersion:${{ env.APP_VERSION }}'))")
|
|
||||||
curl -X GET "https://wa.wibudev.com/code?text=$ENCODED_TEXT&nom=$PHONE"
|
|
||||||
done
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -29,7 +29,9 @@ yarn-error.log*
|
|||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env
|
# env
|
||||||
|
# env local files (keep .env.example)
|
||||||
.env*
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
# QC
|
# QC
|
||||||
QC
|
QC
|
||||||
@@ -50,9 +52,6 @@ next-env.d.ts
|
|||||||
# cache
|
# cache
|
||||||
/cache
|
/cache
|
||||||
|
|
||||||
.github/
|
|
||||||
|
|
||||||
.env.*
|
|
||||||
|
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
|
||||||
|
|||||||
13
.qwen/settings.json
Normal file
13
.qwen/settings.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"playwright-mcp": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"playwright-mcp@latest"
|
||||||
|
],
|
||||||
|
"timeout": 60000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$version": 3
|
||||||
|
}
|
||||||
9
.qwen/settings.json.orig
Normal file
9
.qwen/settings.json.orig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"playwright-mcp": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "playwright-mcp@latest"],
|
||||||
|
"timeout": 60000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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.).
|
||||||
191
DEV-INSPECTOR-ANALYSIS.md
Normal file
191
DEV-INSPECTOR-ANALYSIS.md
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# Dev Inspector - Analisis & Rekomendasi untuk Project Desa Darmasaba
|
||||||
|
|
||||||
|
## 📋 Ringkasan Analisis
|
||||||
|
|
||||||
|
Dokumen `dev-inspector-click-to-source.md` **TIDAK dapat diterapkan langsung** ke project ini karena perbedaan arsitektur fundamental.
|
||||||
|
|
||||||
|
## 🔍 Perbedaan Arsitektur
|
||||||
|
|
||||||
|
| Syarat di Dokumen | Project Desa Darmasaba | Status |
|
||||||
|
|-------------------|------------------------|--------|
|
||||||
|
| **Vite sebagai bundler** | Next.js 15 (Webpack/Turbopack) | ❌ Tidak kompatibel |
|
||||||
|
| **Elysia + Vite middlewareMode** | Next.js App Router + Elysia sebagai API handler | ❌ Berbeda |
|
||||||
|
| **React** | ✅ React 19 | ✅ Kompatibel |
|
||||||
|
| **Bun runtime** | ✅ Bun | ✅ Kompatibel |
|
||||||
|
|
||||||
|
## ✅ Solusi: Next.js Sudah Punya Built-in Click-to-Source
|
||||||
|
|
||||||
|
Next.js memiliki fitur **click-to-source bawaan** yang bekerja tanpa setup tambahan:
|
||||||
|
|
||||||
|
### Cara Menggunakan
|
||||||
|
|
||||||
|
1. **Pastikan dalam development mode:**
|
||||||
|
```bash
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Klik elemen dengan modifier key:**
|
||||||
|
- **macOS**: `Option` + `Click` (atau `⌥` + `Click`)
|
||||||
|
- **Windows/Linux**: `Alt` + `Click`
|
||||||
|
|
||||||
|
3. **File akan terbuka di editor** pada baris dan kolom yang tepat
|
||||||
|
|
||||||
|
### Syarat Agar Berfungsi
|
||||||
|
|
||||||
|
1. **Editor harus ada di PATH**
|
||||||
|
|
||||||
|
VS Code biasanya sudah terdaftar. Jika menggunakan editor lain, set:
|
||||||
|
```bash
|
||||||
|
# Untuk Cursor
|
||||||
|
export EDITOR=cursor
|
||||||
|
|
||||||
|
# Untuk Windsurf
|
||||||
|
export EDITOR=windsurf
|
||||||
|
|
||||||
|
# Untuk Sublime Text
|
||||||
|
export EDITOR=subl
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Hanya berfungsi di development mode**
|
||||||
|
- Fitur ini otomatis tree-shaken di production
|
||||||
|
- Zero overhead di production build
|
||||||
|
|
||||||
|
3. **Browser DevTools harus terbuka** (beberapa browser memerlukan ini)
|
||||||
|
|
||||||
|
## 🎯 Rekomendasi untuk Project Ini
|
||||||
|
|
||||||
|
### Opsi 1: Gunakan Built-in Next.js (DIREKOMENDASIKAN)
|
||||||
|
|
||||||
|
**Kelebihan:**
|
||||||
|
- ✅ Zero setup
|
||||||
|
- ✅ Maintain oleh Vercel
|
||||||
|
- ✅ Otomatis compatible dengan Next.js updates
|
||||||
|
- ✅ Zero production overhead
|
||||||
|
|
||||||
|
**Kekurangan:**
|
||||||
|
- ⚠️ Hotkey berbeda (`Option+Click` vs `Ctrl+Shift+Cmd+C`)
|
||||||
|
- ⚠️ Tidak ada visual overlay/tooltip seperti di dokumen
|
||||||
|
|
||||||
|
**Cara:**
|
||||||
|
Tidak perlu melakukan apapun - fitur sudah aktif saat `bun run dev`.
|
||||||
|
|
||||||
|
### Opsi 2: Custom Implementation (JIKA DIPERLUKAN)
|
||||||
|
|
||||||
|
Jika ingin visual overlay dan tooltip seperti di dokumen, bisa dibuat custom component dengan pendekatan berbeda:
|
||||||
|
|
||||||
|
#### Arsitektur Alternatif untuk Next.js
|
||||||
|
|
||||||
|
```
|
||||||
|
BUILD TIME (Next.js/Webpack):
|
||||||
|
.tsx/.jsx file
|
||||||
|
→ [Custom Webpack Loader] inject data-inspector-* attributes
|
||||||
|
→ [Next.js internal transform] JSX to React.createElement
|
||||||
|
→ Browser menerima elemen dengan attributes
|
||||||
|
|
||||||
|
RUNTIME (Browser):
|
||||||
|
[SAMA seperti dokumen - DevInspector component]
|
||||||
|
|
||||||
|
BACKEND (Next.js API Route):
|
||||||
|
/__open-in-editor → Bun.spawn([editor, '--goto', 'file:line:col'])
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Komponen yang Dibutuhkan:
|
||||||
|
|
||||||
|
1. **Custom Webpack Loader** (bukan Vite Plugin)
|
||||||
|
- Inject attributes via webpack transform
|
||||||
|
- Taruh di `next.config.ts` webpack config
|
||||||
|
|
||||||
|
2. **DevInspector Component** (sama seperti dokumen)
|
||||||
|
- Browser runtime untuk handle hotkey & klik
|
||||||
|
|
||||||
|
3. **API Route `/__open-in-editor`**
|
||||||
|
- Buat sebagai Next.js API route: `src/app/api/__open-in-editor/route.ts`
|
||||||
|
- HARUS bypass auth middleware
|
||||||
|
|
||||||
|
4. **Conditional Import** (sama seperti dokumen)
|
||||||
|
```tsx
|
||||||
|
const InspectorWrapper = process.env.NODE_ENV === 'development'
|
||||||
|
? (await import('./DevInspector')).DevInspector
|
||||||
|
: ({ children }) => <>{children}</>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Implementasi Steps:
|
||||||
|
|
||||||
|
Jika Anda ingin melanjutkan dengan custom implementation, berikut steps:
|
||||||
|
|
||||||
|
1. ✅ Buat `src/components/DevInspector.tsx` (copy dari dokumen)
|
||||||
|
2. ⚠️ Buat webpack loader untuk inject attributes (perlu research)
|
||||||
|
3. ✅ Buat API route `src/app/api/__open-in-editor/route.ts`
|
||||||
|
4. ✅ Wrap root layout dengan DevInspector
|
||||||
|
5. ✅ Set `REACT_EDITOR` di `.env`
|
||||||
|
|
||||||
|
**Peringatan:**
|
||||||
|
- Webpack loader lebih kompleks daripada Vite plugin
|
||||||
|
- Mungkin ada edge cases dengan Next.js internals
|
||||||
|
- Perlu maintenance ekstra saat Next.js update
|
||||||
|
|
||||||
|
## 📊 Perbandingan
|
||||||
|
|
||||||
|
| Fitur | Built-in Next.js | Custom Implementation |
|
||||||
|
|-------|------------------|----------------------|
|
||||||
|
| Setup | ✅ Zero | ⚠️ Medium |
|
||||||
|
| Visual Overlay | ❌ Tidak ada | ✅ Ada |
|
||||||
|
| Tooltip | ❌ Tidak ada | ✅ Ada |
|
||||||
|
| Hotkey | `Option+Click` | Custom (bisa disesuaikan) |
|
||||||
|
| Maintenance | ✅ Vercel | ⚠️ Manual |
|
||||||
|
| Compatibility | ✅ Guaranteed | ⚠️ Perlu testing |
|
||||||
|
| Production Impact | ✅ Zero | ✅ Zero (dengan conditional import) |
|
||||||
|
|
||||||
|
## 🎯 Kesimpulan
|
||||||
|
|
||||||
|
**Rekomendasi: Gunakan Built-in Next.js**
|
||||||
|
|
||||||
|
Alasan:
|
||||||
|
1. ✅ Sudah tersedia - tidak perlu setup
|
||||||
|
2. ✅ Lebih stabil - maintain oleh Vercel
|
||||||
|
3. ✅ Lebih simple - tidak ada custom code
|
||||||
|
4. ✅ Future-proof - otomatis update dengan Next.js
|
||||||
|
|
||||||
|
**Custom implementation hanya diperlukan jika:**
|
||||||
|
- Anda sangat membutuhkan visual overlay & tooltip
|
||||||
|
- Anda ingin hotkey yang sama persis (`Ctrl+Shift+Cmd+C`)
|
||||||
|
- Anda punya waktu untuk maintenance
|
||||||
|
|
||||||
|
## 🚀 Quick Start - Built-in Feature
|
||||||
|
|
||||||
|
Untuk menggunakan click-to-source bawaan Next.js:
|
||||||
|
|
||||||
|
1. Jalankan development server:
|
||||||
|
```bash
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Buka browser ke `http://localhost:3000`
|
||||||
|
|
||||||
|
3. Tahan `Option` (macOS) atau `Alt` (Windows/Linux)
|
||||||
|
|
||||||
|
4. Cursor akan berubah menjadi crosshair
|
||||||
|
|
||||||
|
5. Klik elemen mana pun - file akan terbuka di editor
|
||||||
|
|
||||||
|
6. **Opsional**: Set editor di `.env`:
|
||||||
|
```env
|
||||||
|
# .env.local
|
||||||
|
EDITOR=code # atau cursor, windsurf, subl
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Notes
|
||||||
|
|
||||||
|
- Fitur ini hanya aktif di development mode (`NODE_ENV=development`)
|
||||||
|
- Production build (`bun run build`) otomatis menghilangkan fitur ini
|
||||||
|
- Next.js menggunakan mekanisme yang mirip (source mapping) untuk menentukan lokasi component
|
||||||
|
- Jika editor tidak terbuka, pastikan:
|
||||||
|
- Editor sudah terinstall dan ada di PATH
|
||||||
|
- Browser DevTools terbuka (beberapa browser require ini)
|
||||||
|
- Anda menggunakan development server, bukan production
|
||||||
|
|
||||||
|
## 🔗 Referensi
|
||||||
|
|
||||||
|
- [Next.js Documentation - Launching Editor](https://nextjs.org/docs/app/api-reference/config/next-config-js/reactStrictMode)
|
||||||
|
- [React DevTools - Component Inspection](https://react.dev/learn/react-developer-tools)
|
||||||
|
- [Original Dev Inspector Document](./dev-inspector-click-to-source.md)
|
||||||
74
Dockerfile
Normal file
74
Dockerfile
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# ==============================
|
||||||
|
# Stage 1: Builder
|
||||||
|
# ==============================
|
||||||
|
FROM oven/bun:1-debian AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libc6 \
|
||||||
|
git \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY package.json bun.lockb* ./
|
||||||
|
|
||||||
|
ENV SHARP_IGNORE_GLOBAL_LIBVIPS=1
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN cp .env.example .env || true
|
||||||
|
|
||||||
|
ENV PRISMA_CLI_BINARY_TARGETS=debian-openssl-3.0.x
|
||||||
|
RUN bunx prisma generate
|
||||||
|
|
||||||
|
# Generate API types (opsional)
|
||||||
|
RUN bun run gen:api || echo "tidak ada gen api"
|
||||||
|
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
# ==============================
|
||||||
|
# Stage 2: Runner (Production)
|
||||||
|
# ==============================
|
||||||
|
FROM oven/bun:1-debian AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV PRISMA_CLI_BINARY_TARGETS=debian-openssl-3.0.x
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN groupadd --system --gid 1001 nodejs \
|
||||||
|
&& useradd --system --uid 1001 --gid nodejs nextjs
|
||||||
|
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/next.config.* ./
|
||||||
|
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Create uploads directory with proper permissions
|
||||||
|
RUN mkdir -p /app/uploads && chown nextjs:nodejs /app/uploads
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
# Persistent storage for uploaded files
|
||||||
|
VOLUME ["/app/uploads"]
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["/app/docker-entrypoint.sh"]
|
||||||
173
MUSIK_CREATE_ANALYSIS.md
Normal file
173
MUSIK_CREATE_ANALYSIS.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Musik Desa - Create Feature Analysis
|
||||||
|
|
||||||
|
## Error Summary
|
||||||
|
**Error**: `ERR_BLOCKED_BY_CLIENT` saat create musik di staging environment
|
||||||
|
|
||||||
|
## Root Cause Analysis
|
||||||
|
|
||||||
|
### 1. **CORS Configuration Issue** (Primary)
|
||||||
|
File: `src/app/api/[[...slugs]]/route.ts`
|
||||||
|
|
||||||
|
The CORS configuration has specific origins listed:
|
||||||
|
```typescript
|
||||||
|
const corsConfig = {
|
||||||
|
origin: [
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://localhost:3001",
|
||||||
|
"https://cld-dkr-desa-darmasaba-stg.wibudev.com",
|
||||||
|
"https://cld-dkr-staging-desa-darmasaba.wibudev.com",
|
||||||
|
"*",
|
||||||
|
],
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: The wildcard `*` is at the end, but some browsers don't respect it when `credentials: true` is set.
|
||||||
|
|
||||||
|
### 2. **API Fetch Base URL** (Secondary)
|
||||||
|
File: `src/lib/api-fetch.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**:
|
||||||
|
- In staging, this might still default to `http://localhost:3000`
|
||||||
|
- Mixed content (HTTPS frontend → HTTP API) gets blocked by browsers
|
||||||
|
- The `NEXT_PUBLIC_BASE_URL` environment variable might not be set in staging
|
||||||
|
|
||||||
|
### 3. **File Storage Upload Path** (Tertiary)
|
||||||
|
File: `src/app/api/[[...slugs]]/_lib/fileStorage/_lib/create.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const UPLOAD_DIR = process.env.WIBU_UPLOAD_DIR;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: If `WIBU_UPLOAD_DIR` is not set or points to a non-writable location, uploads will fail silently.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
### Fix 1: Update CORS Configuration
|
||||||
|
**File**: `src/app/api/[[...slugs]]/route.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Move wildcard to first position and ensure it works with credentials
|
||||||
|
const corsConfig = {
|
||||||
|
origin: [
|
||||||
|
"*", // Allow all origins (for staging flexibility)
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://localhost:3001",
|
||||||
|
"https://cld-dkr-desa-darmasaba-stg.wibudev.com",
|
||||||
|
"https://cld-dkr-staging-desa-darmasaba.wibudev.com",
|
||||||
|
"https://desa-darmasaba-stg.wibudev.com"
|
||||||
|
],
|
||||||
|
methods: ["GET", "POST", "PATCH", "DELETE", "PUT", "OPTIONS"] as HTTPMethod[],
|
||||||
|
allowedHeaders: ["Content-Type", "Authorization", "Accept"],
|
||||||
|
exposedHeaders: ["Content-Range", "X-Content-Range"],
|
||||||
|
maxAge: 86400, // 24 hours
|
||||||
|
credentials: true,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix 2: Add Environment Variable Validation
|
||||||
|
**File**: `.env.example` (update)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Application Configuration
|
||||||
|
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# For staging/production, set this to your actual domain
|
||||||
|
# NEXT_PUBLIC_BASE_URL=https://cld-dkr-desa-darmasaba-stg.wibudev.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix 3: Update API Fetch to Handle Relative URLs
|
||||||
|
**File**: `src/lib/api-fetch.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AppServer } from '@/app/api/[[...slugs]]/route'
|
||||||
|
import { treaty } from '@elysiajs/eden'
|
||||||
|
|
||||||
|
// Use relative URL for better deployment flexibility
|
||||||
|
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || '/'
|
||||||
|
|
||||||
|
const ApiFetch = treaty<AppServer>(BASE_URL)
|
||||||
|
|
||||||
|
export default ApiFetch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix 4: Add Error Handling in Create Page
|
||||||
|
**File**: `src/app/admin/(dashboard)/musik/create/page.tsx`
|
||||||
|
|
||||||
|
Add better error logging to diagnose issues:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
// ... validation ...
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
// Upload cover image
|
||||||
|
const coverRes = await ApiFetch.api.fileStorage.create.post({
|
||||||
|
file: coverFile,
|
||||||
|
name: coverFile.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!coverRes.data?.data?.id) {
|
||||||
|
console.error('Cover upload failed:', coverRes);
|
||||||
|
return toast.error('Gagal mengunggah cover, silakan coba lagi');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... rest of the code ...
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating musik:', {
|
||||||
|
error,
|
||||||
|
message: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
stack: error instanceof Error ? error.stack : undefined,
|
||||||
|
});
|
||||||
|
toast.error('Terjadi kesalahan saat membuat musik');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
- [ ] Test create musik with cover image and audio file
|
||||||
|
- [ ] Verify CORS headers in browser DevTools Network tab
|
||||||
|
- [ ] Check that file uploads are saved to correct directory
|
||||||
|
|
||||||
|
### Staging Environment
|
||||||
|
- [ ] Set `NEXT_PUBLIC_BASE_URL` to staging domain
|
||||||
|
- [ ] Verify HTTPS is used for all API calls
|
||||||
|
- [ ] Check browser console for mixed content warnings
|
||||||
|
- [ ] Verify `WIBU_UPLOAD_DIR` is set and writable
|
||||||
|
- [ ] Test create musik end-to-end
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
|
||||||
|
### ERR_BLOCKED_BY_CLIENT Common Causes:
|
||||||
|
1. **CORS policy blocking** - Most likely cause
|
||||||
|
2. **Ad blockers** - Can block certain API endpoints
|
||||||
|
3. **Mixed content** - HTTPS page making HTTP requests
|
||||||
|
4. **Content Security Policy (CSP)** - Restrictive CSP headers
|
||||||
|
5. **Browser extensions** - Privacy/security extensions blocking requests
|
||||||
|
|
||||||
|
### Debugging Steps:
|
||||||
|
1. Open browser DevTools → Network tab
|
||||||
|
2. Try to create musik
|
||||||
|
3. Look for failed requests (red status)
|
||||||
|
4. Check the "Headers" tab for:
|
||||||
|
- Request URL (should be correct domain)
|
||||||
|
- Response headers (should have `Access-Control-Allow-Origin`)
|
||||||
|
- Status code (4xx/5xx indicates server-side issue)
|
||||||
|
5. Check browser console for CORS errors
|
||||||
|
|
||||||
|
## Recommended Next Steps
|
||||||
|
|
||||||
|
1. **Immediate**: Update CORS configuration to allow staging domain
|
||||||
|
2. **Short-term**: Add proper environment variable validation
|
||||||
|
3. **Long-term**: Implement proper error boundaries and logging
|
||||||
5
QWEN.md
5
QWEN.md
@@ -229,4 +229,7 @@ Common issues and solutions:
|
|||||||
3. Test database changes with `bunx prisma db push`
|
3. Test database changes with `bunx prisma db push`
|
||||||
4. Use the integrated Swagger docs at `/api/docs` for API testing
|
4. Use the integrated Swagger docs at `/api/docs` for API testing
|
||||||
5. Check environment variables are properly configured
|
5. Check environment variables are properly configured
|
||||||
6. Verify responsive design on different screen sizes
|
6. Verify responsive design on different screen sizes
|
||||||
|
|
||||||
|
## Qwen Added Memories
|
||||||
|
- **GitHub Workflows**: Project ini memiliki workflow GitHub Action untuk deployment. User akan menangani workflow secara manual di GitHub.
|
||||||
|
|||||||
49
biome.json
Normal file
49
biome.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false,
|
||||||
|
"experimentalScannerIgnores": [
|
||||||
|
"node_modules",
|
||||||
|
".next",
|
||||||
|
"out",
|
||||||
|
"public"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 100
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true,
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedVariables": "warn",
|
||||||
|
"noUnusedImports": "warn"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noExplicitAny": "warn"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"noNonNullAssertion": "warn"
|
||||||
|
},
|
||||||
|
"complexity": {
|
||||||
|
"noForEach": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"trailingCommas": "all",
|
||||||
|
"semicolons": "always"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
553
dev-inspector-click-to-source.md
Normal file
553
dev-inspector-click-to-source.md
Normal file
@@ -0,0 +1,553 @@
|
|||||||
|
# Skill: Dev Inspector — Click-to-Source untuk Bun + Elysia + Vite + React
|
||||||
|
|
||||||
|
## Ringkasan
|
||||||
|
|
||||||
|
Fitur development: klik elemen UI di browser → langsung buka source code di editor (VS Code, Cursor, dll) pada baris dan kolom yang tepat. Zero overhead di production.
|
||||||
|
|
||||||
|
**Hotkey**: `Ctrl+Shift+Cmd+C` (macOS) / `Ctrl+Shift+Alt+C` → aktifkan mode inspect → klik elemen → file terbuka.
|
||||||
|
|
||||||
|
## Kenapa Tidak Pakai Library
|
||||||
|
|
||||||
|
`react-dev-inspector` crash di React 19 karena:
|
||||||
|
- `fiber.return.child.sibling` bisa null di React 19
|
||||||
|
- `_debugSource` dihapus dari React 19
|
||||||
|
- Walking fiber tree tidak stabil antar versi React
|
||||||
|
|
||||||
|
Solusi ini **regex-based + multi-fallback**, tidak bergantung pada React internals.
|
||||||
|
|
||||||
|
## Syarat Arsitektur
|
||||||
|
|
||||||
|
Fitur ini bekerja karena 4 syarat struktural terpenuhi. Jika salah satu tidak ada, fitur tidak bisa diimplementasi atau perlu adaptasi signifikan.
|
||||||
|
|
||||||
|
### 1. Vite sebagai Bundler (Wajib)
|
||||||
|
|
||||||
|
Seluruh mekanisme bergantung pada **Vite plugin transform pipeline**:
|
||||||
|
- `inspectorPlugin()` inject attributes ke JSX saat build/HMR
|
||||||
|
- `enforce: 'pre'` memastikan plugin jalan sebelum OXC/Babel transform JSX
|
||||||
|
- `import.meta.env?.DEV` sebagai compile-time constant untuk tree-shaking
|
||||||
|
|
||||||
|
**Tidak bisa diganti dengan**: esbuild standalone, webpack (perlu loader berbeda), SWC standalone.
|
||||||
|
**Bisa diganti dengan**: framework yang pakai Vite di dalamnya (Remix Vite, TanStack Start, Astro).
|
||||||
|
|
||||||
|
### 2. Server dan Frontend dalam Satu Proses (Wajib)
|
||||||
|
|
||||||
|
Endpoint `/__open-in-editor` harus **satu proses dengan dev server** yang melayani frontend:
|
||||||
|
- Browser POST ke origin yang sama (no CORS)
|
||||||
|
- Server punya akses ke filesystem lokal untuk `Bun.spawn(editor)`
|
||||||
|
- Endpoint harus bisa ditangani **sebelum routing & middleware** (auth, tenant, dll)
|
||||||
|
|
||||||
|
**Pola yang memenuhi syarat:**
|
||||||
|
- Elysia + Vite middlewareMode (project ini) — `onRequest` intercept sebelum route matching
|
||||||
|
- Express/Fastify + Vite middlewareMode — middleware biasa sebelum auth
|
||||||
|
- Vite dev server standalone (`vite dev`) — pakai `configureServer` hook
|
||||||
|
|
||||||
|
**Tidak memenuhi syarat:**
|
||||||
|
- Frontend dan backend di proses/port terpisah (misal: CRA + separate API server) — perlu proxy atau CORS config tambahan
|
||||||
|
- Serverless/edge deployment — tidak bisa `spawn` editor
|
||||||
|
|
||||||
|
### 3. React sebagai UI Framework (Wajib untuk Multi-Fallback)
|
||||||
|
|
||||||
|
Strategi extraction source info bergantung pada React internals:
|
||||||
|
1. `__reactProps$*` — React menyimpan props di DOM element
|
||||||
|
2. `__reactFiber$*` — React fiber tree untuk walk-up
|
||||||
|
3. DOM attribute — fallback universal
|
||||||
|
|
||||||
|
**Jika pakai framework lain** (Vue, Svelte, Solid):
|
||||||
|
- Hanya strategi 3 (DOM attribute) yang berfungsi — tetap cukup
|
||||||
|
- Hapus strategi 1 & 2 dari `getCodeInfoFromElement()`
|
||||||
|
- Inject attributes tetap via Vite plugin (framework-agnostic)
|
||||||
|
|
||||||
|
### 4. Bun sebagai Runtime (Direkomendasikan, Bukan Wajib)
|
||||||
|
|
||||||
|
Bun memberikan API yang lebih clean:
|
||||||
|
- `Bun.spawn()` — fire-and-forget tanpa import
|
||||||
|
- `Bun.which()` — cek executable ada di PATH (mencegah uncatchable error)
|
||||||
|
|
||||||
|
**Jika pakai Node.js:**
|
||||||
|
- `Bun.spawn()` → `child_process.spawn(editor, args, { detached: true, stdio: 'ignore' }).unref()`
|
||||||
|
- `Bun.which()` → `const which = require('which'); which.sync(editor, { nothrow: true })`
|
||||||
|
|
||||||
|
### Ringkasan Syarat
|
||||||
|
|
||||||
|
| Syarat | Wajib? | Alternatif |
|
||||||
|
|-------------------------------|----------|------------------------------------------------------|
|
||||||
|
| Vite sebagai bundler | Ya | Framework berbasis Vite (Remix, Astro, dll) |
|
||||||
|
| Server + frontend satu proses | Ya | Bisa diakali dengan proxy, tapi tambah kompleksitas |
|
||||||
|
| React | Sebagian | Framework lain bisa, hanya fallback ke DOM attribute |
|
||||||
|
| Bun runtime | Tidak | Node.js dengan `child_process` + `which` package |
|
||||||
|
|
||||||
|
## Arsitektur
|
||||||
|
|
||||||
|
```
|
||||||
|
BUILD TIME (Vite Plugin):
|
||||||
|
.tsx/.jsx file
|
||||||
|
→ [inspectorPlugin enforce:'pre'] inject data-inspector-* attributes ke JSX
|
||||||
|
→ [react() OXC] transform JSX ke createElement
|
||||||
|
→ Browser menerima elemen dengan attributes
|
||||||
|
|
||||||
|
RUNTIME (Browser):
|
||||||
|
Hotkey → aktifkan mode → hover elemen → baca attributes → klik
|
||||||
|
→ POST /__open-in-editor {relativePath, line, column}
|
||||||
|
|
||||||
|
BACKEND (Elysia onRequest):
|
||||||
|
/__open-in-editor → Bun.spawn([editor, '--goto', 'file:line:col'])
|
||||||
|
→ Editor terbuka di lokasi tepat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Komponen yang Dibutuhkan
|
||||||
|
|
||||||
|
### 1. Vite Plugin — `inspectorPlugin()` (enforce: 'pre')
|
||||||
|
|
||||||
|
Inject `data-inspector-*` ke setiap JSX opening tag via regex.
|
||||||
|
|
||||||
|
**HARUS `enforce: 'pre'`** — kalau tidak, OXC transform JSX duluan dan regex tidak bisa menemukan `<Component`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Taruh di file vite config (misal: src/vite.ts atau vite.config.ts)
|
||||||
|
import path from 'node:path'
|
||||||
|
import type { Plugin } from 'vite'
|
||||||
|
|
||||||
|
function inspectorPlugin(): Plugin {
|
||||||
|
const rootDir = process.cwd()
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'inspector-inject',
|
||||||
|
enforce: 'pre',
|
||||||
|
transform(code, id) {
|
||||||
|
// Hanya .tsx/.jsx, skip node_modules
|
||||||
|
if (!/\.[jt]sx(\?|$)/.test(id) || id.includes('node_modules')) return null
|
||||||
|
if (!code.includes('<')) return null
|
||||||
|
|
||||||
|
const relativePath = path.relative(rootDir, id)
|
||||||
|
let modified = false
|
||||||
|
const lines = code.split('\n')
|
||||||
|
const result: string[] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i]
|
||||||
|
// Match JSX opening tags: <Component atau <div
|
||||||
|
// Skip TypeScript generics (Record<string>) via charBefore check
|
||||||
|
const jsxPattern = /(<(?:[A-Z][a-zA-Z0-9.]*|[a-z][a-zA-Z0-9-]*))\b/g
|
||||||
|
let match: RegExpExecArray | null = null
|
||||||
|
|
||||||
|
while ((match = jsxPattern.exec(line)) !== null) {
|
||||||
|
// Skip jika karakter sebelum `<` adalah identifier char (TypeScript generic)
|
||||||
|
const charBefore = match.index > 0 ? line[match.index - 1] : ''
|
||||||
|
if (/[a-zA-Z0-9_$.]/.test(charBefore)) continue
|
||||||
|
|
||||||
|
const col = match.index + 1
|
||||||
|
const attr = ` data-inspector-line="${i + 1}" data-inspector-column="${col}" data-inspector-relative-path="${relativePath}"`
|
||||||
|
const insertPos = match.index + match[0].length
|
||||||
|
line = line.slice(0, insertPos) + attr + line.slice(insertPos)
|
||||||
|
modified = true
|
||||||
|
jsxPattern.lastIndex += attr.length
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modified) return null
|
||||||
|
return result.join('\n')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mengapa regex, bukan Babel?**
|
||||||
|
- `@vitejs/plugin-react` v6+ pakai OXC (Rust), bukan Babel
|
||||||
|
- Config `babel: { plugins: [...] }` di plugin-react **DIABAIKAN**
|
||||||
|
- Regex jalan sebelum OXC via `enforce: 'pre'`
|
||||||
|
|
||||||
|
**Gotcha: TypeScript generics**
|
||||||
|
- `Record<string>` → karakter sebelum `<` adalah `d` (identifier) → SKIP
|
||||||
|
- `<Button` → karakter sebelum `<` adalah space/newline → MATCH
|
||||||
|
|
||||||
|
### 2. Vite Plugin Order (KRITIS)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
plugins: [
|
||||||
|
// 1. Route generation (jika pakai TanStack Router)
|
||||||
|
TanStackRouterVite({ ... }),
|
||||||
|
|
||||||
|
// 2. Inspector inject — HARUS sebelum react()
|
||||||
|
inspectorPlugin(),
|
||||||
|
|
||||||
|
// 3. React OXC transform
|
||||||
|
react(),
|
||||||
|
|
||||||
|
// 4. (Opsional) Dedupe React Refresh untuk middlewareMode
|
||||||
|
dedupeRefreshPlugin(),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Jika urutan salah (inspectorPlugin setelah react):**
|
||||||
|
- OXC transform `<Button>` → `React.createElement(Button, ...)`
|
||||||
|
- Regex tidak menemukan `<Button` → attributes TIDAK ter-inject
|
||||||
|
- Fitur tidak berfungsi, tanpa error
|
||||||
|
|
||||||
|
### 3. DevInspector Component (Browser Runtime)
|
||||||
|
|
||||||
|
Komponen React yang handle hotkey, overlay, dan klik.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/frontend/DevInspector.tsx
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
interface CodeInfo {
|
||||||
|
relativePath: string
|
||||||
|
line: string
|
||||||
|
column: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Baca data-inspector-* dari fiber props atau DOM attributes */
|
||||||
|
function getCodeInfoFromElement(element: HTMLElement): CodeInfo | null {
|
||||||
|
// Strategi 1: React internal props __reactProps$ (paling akurat)
|
||||||
|
for (const key of Object.keys(element)) {
|
||||||
|
if (key.startsWith('__reactProps$')) {
|
||||||
|
const props = (element as any)[key]
|
||||||
|
if (props?.['data-inspector-relative-path']) {
|
||||||
|
return {
|
||||||
|
relativePath: props['data-inspector-relative-path'],
|
||||||
|
line: props['data-inspector-line'] || '1',
|
||||||
|
column: props['data-inspector-column'] || '1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Strategi 2: Walk fiber tree __reactFiber$
|
||||||
|
if (key.startsWith('__reactFiber$')) {
|
||||||
|
const fiber = (element as any)[key]
|
||||||
|
let f = fiber
|
||||||
|
while (f) {
|
||||||
|
const p = f.pendingProps || f.memoizedProps
|
||||||
|
if (p?.['data-inspector-relative-path']) {
|
||||||
|
return {
|
||||||
|
relativePath: p['data-inspector-relative-path'],
|
||||||
|
line: p['data-inspector-line'] || '1',
|
||||||
|
column: p['data-inspector-column'] || '1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback: _debugSource (React < 19)
|
||||||
|
const src = f._debugSource ?? f._debugOwner?._debugSource
|
||||||
|
if (src?.fileName && src?.lineNumber) {
|
||||||
|
return {
|
||||||
|
relativePath: src.fileName,
|
||||||
|
line: String(src.lineNumber),
|
||||||
|
column: String(src.columnNumber ?? 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f = f.return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategi 3: Fallback DOM attribute langsung
|
||||||
|
const rp = element.getAttribute('data-inspector-relative-path')
|
||||||
|
if (rp) {
|
||||||
|
return {
|
||||||
|
relativePath: rp,
|
||||||
|
line: element.getAttribute('data-inspector-line') || '1',
|
||||||
|
column: element.getAttribute('data-inspector-column') || '1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Walk up DOM tree sampai ketemu elemen yang punya source info */
|
||||||
|
function findCodeInfo(target: HTMLElement): CodeInfo | null {
|
||||||
|
let el: HTMLElement | null = target
|
||||||
|
while (el) {
|
||||||
|
const info = getCodeInfoFromElement(el)
|
||||||
|
if (info) return info
|
||||||
|
el = el.parentElement
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function openInEditor(info: CodeInfo) {
|
||||||
|
fetch('/__open-in-editor', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
relativePath: info.relativePath,
|
||||||
|
lineNumber: info.line,
|
||||||
|
columnNumber: info.column,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DevInspector({ children }: { children: React.ReactNode }) {
|
||||||
|
const [active, setActive] = useState(false)
|
||||||
|
const overlayRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const tooltipRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
const lastInfoRef = useRef<CodeInfo | null>(null)
|
||||||
|
|
||||||
|
const updateOverlay = useCallback((target: HTMLElement | null) => {
|
||||||
|
const ov = overlayRef.current
|
||||||
|
const tt = tooltipRef.current
|
||||||
|
if (!ov || !tt) return
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
ov.style.display = 'none'
|
||||||
|
tt.style.display = 'none'
|
||||||
|
lastInfoRef.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = findCodeInfo(target)
|
||||||
|
if (!info) {
|
||||||
|
ov.style.display = 'none'
|
||||||
|
tt.style.display = 'none'
|
||||||
|
lastInfoRef.current = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInfoRef.current = info
|
||||||
|
|
||||||
|
const rect = target.getBoundingClientRect()
|
||||||
|
ov.style.display = 'block'
|
||||||
|
ov.style.top = `${rect.top + window.scrollY}px`
|
||||||
|
ov.style.left = `${rect.left + window.scrollX}px`
|
||||||
|
ov.style.width = `${rect.width}px`
|
||||||
|
ov.style.height = `${rect.height}px`
|
||||||
|
|
||||||
|
tt.style.display = 'block'
|
||||||
|
tt.textContent = `${info.relativePath}:${info.line}`
|
||||||
|
const ttTop = rect.top + window.scrollY - 24
|
||||||
|
tt.style.top = `${ttTop > 0 ? ttTop : rect.bottom + window.scrollY + 4}px`
|
||||||
|
tt.style.left = `${rect.left + window.scrollX}px`
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Activate/deactivate event listeners
|
||||||
|
useEffect(() => {
|
||||||
|
if (!active) return
|
||||||
|
|
||||||
|
const onMouseOver = (e: MouseEvent) => updateOverlay(e.target as HTMLElement)
|
||||||
|
|
||||||
|
const onClick = (e: MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const info = lastInfoRef.current ?? findCodeInfo(e.target as HTMLElement)
|
||||||
|
if (info) {
|
||||||
|
const loc = `${info.relativePath}:${info.line}:${info.column}`
|
||||||
|
console.log('[DevInspector] Open:', loc)
|
||||||
|
navigator.clipboard.writeText(loc)
|
||||||
|
openInEditor(info)
|
||||||
|
}
|
||||||
|
setActive(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') setActive(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mouseover', onMouseOver, true)
|
||||||
|
document.addEventListener('click', onClick, true)
|
||||||
|
document.addEventListener('keydown', onKeyDown)
|
||||||
|
document.body.style.cursor = 'crosshair'
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mouseover', onMouseOver, true)
|
||||||
|
document.removeEventListener('click', onClick, true)
|
||||||
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
if (overlayRef.current) overlayRef.current.style.display = 'none'
|
||||||
|
if (tooltipRef.current) tooltipRef.current.style.display = 'none'
|
||||||
|
}
|
||||||
|
}, [active, updateOverlay])
|
||||||
|
|
||||||
|
// Hotkey: Ctrl+Shift+Cmd+C (macOS) / Ctrl+Shift+Alt+C
|
||||||
|
useEffect(() => {
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key.toLowerCase() === 'c' && e.ctrlKey && e.shiftKey && (e.metaKey || e.altKey)) {
|
||||||
|
e.preventDefault()
|
||||||
|
setActive((prev) => !prev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('keydown', onKeyDown)
|
||||||
|
return () => document.removeEventListener('keydown', onKeyDown)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
<div
|
||||||
|
ref={overlayRef}
|
||||||
|
style={{
|
||||||
|
display: 'none',
|
||||||
|
position: 'absolute',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
border: '2px solid #3b82f6',
|
||||||
|
backgroundColor: 'rgba(59,130,246,0.1)',
|
||||||
|
zIndex: 99999,
|
||||||
|
transition: 'all 0.05s ease',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
ref={tooltipRef}
|
||||||
|
style={{
|
||||||
|
display: 'none',
|
||||||
|
position: 'absolute',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
backgroundColor: '#1e293b',
|
||||||
|
color: '#e2e8f0',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
padding: '2px 6px',
|
||||||
|
borderRadius: '3px',
|
||||||
|
zIndex: 100000,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Backend Endpoint — `/__open-in-editor`
|
||||||
|
|
||||||
|
**HARUS ditangani di `onRequest` / sebelum middleware**, bukan sebagai route biasa. Kalau jadi route, akan kena auth middleware dan gagal.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Di entry point server (src/index.tsx), dalam onRequest handler:
|
||||||
|
|
||||||
|
if (!isProduction && pathname === '/__open-in-editor' && request.method === 'POST') {
|
||||||
|
const { relativePath, lineNumber, columnNumber } = (await request.json()) as {
|
||||||
|
relativePath: string
|
||||||
|
lineNumber: string
|
||||||
|
columnNumber: string
|
||||||
|
}
|
||||||
|
const file = `${process.cwd()}/${relativePath}`
|
||||||
|
const editor = process.env.REACT_EDITOR || 'code'
|
||||||
|
const loc = `${file}:${lineNumber}:${columnNumber}`
|
||||||
|
const args = editor === 'subl' ? [loc] : ['--goto', loc]
|
||||||
|
const editorPath = Bun.which(editor)
|
||||||
|
console.log(`[inspector] ${editor} → ${editorPath ?? 'NOT FOUND'} → ${loc}`)
|
||||||
|
if (editorPath) {
|
||||||
|
Bun.spawn([editor, ...args], { stdio: ['ignore', 'ignore', 'ignore'] })
|
||||||
|
} else {
|
||||||
|
console.error(`[inspector] Editor "${editor}" not found in PATH. Set REACT_EDITOR in .env`)
|
||||||
|
}
|
||||||
|
return new Response('ok')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Penting — `Bun.which()` sebelum `Bun.spawn()`:**
|
||||||
|
- `Bun.spawn()` throw native error yang TIDAK bisa di-catch jika executable tidak ada
|
||||||
|
- `Bun.which()` return null dengan aman → cek dulu sebelum spawn
|
||||||
|
|
||||||
|
**Editor yang didukung:**
|
||||||
|
|
||||||
|
| REACT_EDITOR | Editor | Args |
|
||||||
|
|------------------|--------------|--------------------------------|
|
||||||
|
| `code` (default) | VS Code | `--goto file:line:col` |
|
||||||
|
| `cursor` | Cursor | `--goto file:line:col` |
|
||||||
|
| `windsurf` | Windsurf | `--goto file:line:col` |
|
||||||
|
| `subl` | Sublime Text | `file:line:col` (tanpa --goto) |
|
||||||
|
|
||||||
|
### 5. Frontend Entry — Conditional Import (Zero Production Overhead)
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// src/frontend.tsx (atau entry point React)
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
const InspectorWrapper = import.meta.env?.DEV
|
||||||
|
? (await import('./frontend/DevInspector')).DevInspector
|
||||||
|
: ({ children }: { children: ReactNode }) => <>{children}</>
|
||||||
|
|
||||||
|
const app = (
|
||||||
|
<InspectorWrapper>
|
||||||
|
<App />
|
||||||
|
</InspectorWrapper>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bagaimana zero overhead tercapai:**
|
||||||
|
- `import.meta.env?.DEV` adalah compile-time constant
|
||||||
|
- Production build: `false` → dynamic import TIDAK dieksekusi
|
||||||
|
- Tree-shaking menghapus seluruh `DevInspector.tsx` dari bundle
|
||||||
|
- Tidak ada runtime check, tidak ada dead code di bundle
|
||||||
|
|
||||||
|
### 6. (Opsional) Dedupe React Refresh — Workaround Vite middlewareMode
|
||||||
|
|
||||||
|
Jika pakai Vite dalam `middlewareMode` (seperti di Elysia/Express), `@vitejs/plugin-react` v6 bisa inject React Refresh footer dua kali → error "already declared".
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function dedupeRefreshPlugin(): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'dedupe-react-refresh',
|
||||||
|
enforce: 'post',
|
||||||
|
transform(code, id) {
|
||||||
|
if (!/\.[jt]sx(\?|$)/.test(id) || id.includes('node_modules')) return null
|
||||||
|
|
||||||
|
const marker = 'import * as RefreshRuntime from "/@react-refresh"'
|
||||||
|
const firstIdx = code.indexOf(marker)
|
||||||
|
if (firstIdx === -1) return null
|
||||||
|
|
||||||
|
const secondIdx = code.indexOf(marker, firstIdx + marker.length)
|
||||||
|
if (secondIdx === -1) return null
|
||||||
|
|
||||||
|
const sourcemapIdx = code.indexOf('\n//# sourceMappingURL=', secondIdx)
|
||||||
|
const endIdx = sourcemapIdx !== -1 ? sourcemapIdx : code.length
|
||||||
|
|
||||||
|
const cleaned = code.slice(0, secondIdx) + code.slice(endIdx)
|
||||||
|
return { code: cleaned, map: null }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Langkah Implementasi di Project Baru
|
||||||
|
|
||||||
|
### Prasyarat
|
||||||
|
- Runtime: Bun
|
||||||
|
- Server: Elysia (atau framework lain dengan onRequest/beforeHandle)
|
||||||
|
- Frontend: React + Vite
|
||||||
|
- `@vitejs/plugin-react` (OXC)
|
||||||
|
|
||||||
|
### Step-by-step
|
||||||
|
|
||||||
|
1. **Buat `DevInspector.tsx`** — copy komponen dari Bagian 3 ke folder frontend
|
||||||
|
2. **Tambah `inspectorPlugin()`** — copy fungsi dari Bagian 1 ke file vite config
|
||||||
|
3. **Atur plugin order** — `inspectorPlugin()` SEBELUM `react()` (Bagian 2)
|
||||||
|
4. **Tambah endpoint `/__open-in-editor`** — di `onRequest` handler (Bagian 4)
|
||||||
|
5. **Wrap root app** — conditional import di entry point (Bagian 5)
|
||||||
|
6. **Set env** — `REACT_EDITOR=code` (atau cursor/windsurf/subl) di `.env`
|
||||||
|
7. **(Opsional)** Tambah `dedupeRefreshPlugin()` jika pakai Vite `middlewareMode`
|
||||||
|
|
||||||
|
### Checklist Verifikasi
|
||||||
|
|
||||||
|
- [ ] `inspectorPlugin` punya `enforce: 'pre'`
|
||||||
|
- [ ] Plugin order: inspector → react (bukan sebaliknya)
|
||||||
|
- [ ] Endpoint `/__open-in-editor` di LUAR middleware auth
|
||||||
|
- [ ] `Bun.which(editor)` dipanggil SEBELUM `Bun.spawn()`
|
||||||
|
- [ ] Conditional import pakai `import.meta.env?.DEV`
|
||||||
|
- [ ] `REACT_EDITOR` di `.env` sesuai editor yang dipakai
|
||||||
|
- [ ] Hotkey berfungsi: `Ctrl+Shift+Cmd+C` / `Ctrl+Shift+Alt+C`
|
||||||
|
|
||||||
|
## Gotcha & Pelajaran
|
||||||
|
|
||||||
|
| Masalah | Penyebab | Solusi |
|
||||||
|
|----------------------------------|---------------------------------------------|-----------------------------------------------|
|
||||||
|
| Attributes tidak ter-inject | Plugin order salah | `enforce: 'pre'`, taruh sebelum `react()` |
|
||||||
|
| `Record<string>` ikut ter-inject | Regex match TypeScript generics | Cek `charBefore` — skip jika identifier char |
|
||||||
|
| `Bun.spawn` crash | Editor tidak ada di PATH | Selalu `Bun.which()` dulu |
|
||||||
|
| Hotkey tidak response | `e.key` return 'C' (uppercase) karena Shift | Pakai `e.key.toLowerCase()` |
|
||||||
|
| React Refresh duplicate | Vite middlewareMode bug | `dedupeRefreshPlugin()` enforce: 'post' |
|
||||||
|
| Endpoint kena auth middleware | Didaftarkan sebagai route biasa | Tangani di `onRequest` sebelum routing |
|
||||||
|
| `_debugSource` undefined | React 19 menghapusnya | Multi-fallback: reactProps → fiber → DOM attr |
|
||||||
|
|
||||||
|
## Adaptasi untuk Framework Lain
|
||||||
|
|
||||||
|
### Express/Fastify (bukan Elysia)
|
||||||
|
- Endpoint `/__open-in-editor`: gunakan middleware biasa SEBELUM auth
|
||||||
|
- `Bun.spawn` → `child_process.spawn` jika pakai Node.js
|
||||||
|
- `Bun.which` → `which` npm package jika pakai Node.js
|
||||||
|
|
||||||
|
### Next.js
|
||||||
|
- Tidak perlu — Next.js punya built-in click-to-source
|
||||||
|
- Tapi jika ingin custom: taruh endpoint di `middleware.ts`, plugin di `next.config.js`
|
||||||
|
|
||||||
|
### Remix/Tanstack Start (SSR)
|
||||||
|
- Plugin tetap sama (Vite-based)
|
||||||
|
- Endpoint perlu di server entry, bukan di route loader
|
||||||
13
docker-entrypoint.sh
Normal file
13
docker-entrypoint.sh
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔄 Running database migrations..."
|
||||||
|
cd /app
|
||||||
|
bunx prisma migrate deploy || {
|
||||||
|
echo "❌ Migration failed!"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "✅ Migrations completed successfully"
|
||||||
|
|
||||||
|
echo "🚀 Starting application..."
|
||||||
|
exec bun start
|
||||||
@@ -11,6 +11,11 @@ const compat = new FlatCompat({
|
|||||||
|
|
||||||
const eslintConfig = [
|
const eslintConfig = [
|
||||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
|
serverExternalPackages: ['@elysiajs/static', 'elysia'],
|
||||||
experimental: {},
|
experimental: {},
|
||||||
allowedDevOrigins: [
|
allowedDevOrigins: [
|
||||||
"http://192.168.1.82:3000", // buat akses dari HP/device lain
|
"http://192.168.1.82:3000", // buat akses dari HP/device lain
|
||||||
|
|||||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "desa-darmasaba",
|
"name": "desa-darmasaba",
|
||||||
"version": "0.1.5",
|
"version": "0.1.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"test:api": "vitest run",
|
"test:api": "vitest run",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"test": "bun run test:api && bun run test:e2e"
|
"test": "bun run test:api && bun run test:e2e",
|
||||||
|
"gen:api": ""
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "bun run prisma/seed.ts"
|
"seed": "bun run prisma/seed.ts"
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
"@mantine/modals": "^8.3.6",
|
"@mantine/modals": "^8.3.6",
|
||||||
"@mantine/tiptap": "^7.17.4",
|
"@mantine/tiptap": "^7.17.4",
|
||||||
"@paljs/types": "^8.1.0",
|
"@paljs/types": "^8.1.0",
|
||||||
"@prisma/client": "^6.3.1",
|
"@prisma/client": "6.3.1",
|
||||||
"@tabler/icons-react": "^3.30.0",
|
"@tabler/icons-react": "^3.30.0",
|
||||||
"@tiptap/extension-highlight": "^2.11.7",
|
"@tiptap/extension-highlight": "^2.11.7",
|
||||||
"@tiptap/extension-link": "^2.11.7",
|
"@tiptap/extension-link": "^2.11.7",
|
||||||
@@ -70,7 +71,7 @@
|
|||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"form-data": "^4.0.2",
|
"form-data": "^4.0.2",
|
||||||
"framer-motion": "^12.23.5",
|
"framer-motion": "^12.38.0",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"iron-session": "^8.0.4",
|
"iron-session": "^8.0.4",
|
||||||
"jose": "^6.1.0",
|
"jose": "^6.1.0",
|
||||||
@@ -89,7 +90,7 @@
|
|||||||
"p-limit": "^6.2.0",
|
"p-limit": "^6.2.0",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primereact": "^10.9.6",
|
"primereact": "^10.9.6",
|
||||||
"prisma": "^6.3.1",
|
"prisma": "6.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-exif-orientation-img": "^0.1.5",
|
"react-exif-orientation-img": "^0.1.5",
|
||||||
@@ -100,7 +101,7 @@
|
|||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-zoom-pan-pinch": "^3.7.0",
|
"react-zoom-pan-pinch": "^3.7.0",
|
||||||
"readdirp": "^4.1.1",
|
"readdirp": "^4.1.1",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^3.8.0",
|
||||||
"sharp": "^0.34.3",
|
"sharp": "^0.34.3",
|
||||||
"swr": "^2.3.2",
|
"swr": "^2.3.2",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
@@ -120,10 +121,11 @@
|
|||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@vitest/ui": "^4.0.18",
|
"@vitest/ui": "^4.0.18",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.1.6",
|
"eslint-config-next": "15.5.12",
|
||||||
"jsdom": "^28.0.0",
|
"jsdom": "^28.0.0",
|
||||||
"msw": "^2.12.9",
|
"msw": "^2.12.9",
|
||||||
"parcel": "^2.6.2",
|
"parcel": "^2.6.2",
|
||||||
|
"playwright-mcp": "^0.0.19",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
|
|||||||
57
prisma/_seeder_list/core/seed_app_menu.ts
Normal file
57
prisma/_seeder_list/core/seed_app_menu.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const appMenuJson = loadJsonData("core/app-menu.json");
|
||||||
|
const appMenuChildJson = loadJsonData("core/app-menu-child.json");
|
||||||
|
|
||||||
|
export async function seedAppMenu() {
|
||||||
|
console.log("🔄 Seeding AppMenu...");
|
||||||
|
|
||||||
|
for (const item of appMenuJson) {
|
||||||
|
await prisma.appMenu.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
link: item.link,
|
||||||
|
isActive: item.isActive,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
link: item.link,
|
||||||
|
isActive: item.isActive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ AppMenu seeded: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 AppMenu seed selesai");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function seedAppMenuChild() {
|
||||||
|
console.log("🔄 Seeding AppMenuChild...");
|
||||||
|
|
||||||
|
for (const item of appMenuChildJson) {
|
||||||
|
await prisma.appMenuChild.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
link: item.link,
|
||||||
|
isActive: item.isActive,
|
||||||
|
appMenuId: item.appMenuId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
link: item.link,
|
||||||
|
isActive: item.isActive,
|
||||||
|
appMenuId: item.appMenuId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ AppMenuChild seeded: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 AppMenuChild seed selesai");
|
||||||
|
}
|
||||||
69
prisma/_seeder_list/core/seed_core.ts
Normal file
69
prisma/_seeder_list/core/seed_core.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const layananJson = loadJsonData("core/layanan.json");
|
||||||
|
const potensiJson = loadJsonData("core/potensi.json");
|
||||||
|
const landingPageLayananJson = loadJsonData("core/landingpage-layanan.json");
|
||||||
|
|
||||||
|
export async function seedLayananCore() {
|
||||||
|
console.log("🔄 Seeding Layanan...");
|
||||||
|
|
||||||
|
for (const item of layananJson) {
|
||||||
|
await prisma.layanan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Layanan seeded: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 Layanan seed selesai");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function seedPotensiCore() {
|
||||||
|
console.log("🔄 Seeding Potensi...");
|
||||||
|
|
||||||
|
for (const item of potensiJson) {
|
||||||
|
await prisma.potensi.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Potensi seeded: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 Potensi seed selesai");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function seedLandingPageLayanan() {
|
||||||
|
console.log("🔄 Seeding LandingPage_Layanan...");
|
||||||
|
|
||||||
|
for (const item of landingPageLayananJson) {
|
||||||
|
await prisma.landingPage_Layanan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
deksripsi: item.deksripsi,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
deksripsi: item.deksripsi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ LandingPage_Layanan seeded: ${item.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 LandingPage_Layanan seed selesai");
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kategoriBerita from "../../../data/desa/berita/kategori-berita.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
import beritaJson from "../../../data/desa/berita/berita.json";
|
|
||||||
|
const kategoriBerita = loadJsonData("desa/berita/kategori-berita.json");
|
||||||
|
const beritaJson = loadJsonData("desa/berita/berita.json");
|
||||||
|
|
||||||
export async function seedBerita() {
|
export async function seedBerita() {
|
||||||
// ================== SUBMENU BERITA ========================
|
// ================== SUBMENU BERITA ========================
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import foto from "../../../../data/desa/gallery/foto/foto.json";
|
import { loadJsonData } from "../../../../load-json";
|
||||||
|
|
||||||
|
const foto = loadJsonData("desa/gallery/foto/foto.json");
|
||||||
|
|
||||||
export async function seedFoto() {
|
export async function seedFoto() {
|
||||||
console.log("🔄 Seeding Foto...");
|
console.log("🔄 Seeding Foto...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import galleryVideo from "../../../../data/desa/gallery/video/video.json";
|
import { loadJsonData } from "../../../../load-json";
|
||||||
|
|
||||||
|
const galleryVideo = loadJsonData("desa/gallery/video/video.json");
|
||||||
|
|
||||||
export async function seedVideo() {
|
export async function seedVideo() {
|
||||||
console.log("🔄 Seeding Gallery Video...");
|
console.log("🔄 Seeding Gallery Video...");
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import pelayananSuratKeterangan from "../../../data/desa/layanan/pelayananSuratKeterangan.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
import pelayananTelunjukSaktiDesa from "../../../data/desa/layanan/pelayananTelunjukSaktiDesa.json";
|
|
||||||
import pelayananPerizinanBerusaha from "../../../data/desa/layanan/pelayananPerizinanBerusaha.json";
|
const pelayananSuratKeterangan = loadJsonData("desa/layanan/pelayananSuratKeterangan.json");
|
||||||
import pelayananPendudukNonPermanen from "../../../data/desa/layanan/pelayananPendudukNonPermanen.json";
|
const pelayananTelunjukSaktiDesa = loadJsonData("desa/layanan/pelayananTelunjukSaktiDesa.json");
|
||||||
|
const pelayananPerizinanBerusaha = loadJsonData("desa/layanan/pelayananPerizinanBerusaha.json");
|
||||||
|
const pelayananPendudukNonPermanen = loadJsonData("desa/layanan/pelayananPendudukNonPermanen.json");
|
||||||
|
|
||||||
export async function seedLayanan() {
|
export async function seedLayanan() {
|
||||||
console.log("🔄 Seeding Pelayanan Surat Keterangan...");
|
console.log("🔄 Seeding Pelayanan Surat Keterangan...");
|
||||||
|
|||||||
57
prisma/_seeder_list/desa/musik-desa/seed_musik_desa.ts
Normal file
57
prisma/_seeder_list/desa/musik-desa/seed_musik_desa.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const musikJson = loadJsonData("desa/musik-desa/musik-desa.json");
|
||||||
|
|
||||||
|
export async function seedMusikDesa() {
|
||||||
|
console.log("Seeding Musik Desa...");
|
||||||
|
|
||||||
|
for (const item of musikJson) {
|
||||||
|
let audioFileId: string | null = null;
|
||||||
|
let coverImageId: string | null = null;
|
||||||
|
|
||||||
|
if (item.audioFileName) {
|
||||||
|
const audio = await prisma.fileStorage.findUnique({
|
||||||
|
where: { name: item.audioFileName },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (audio) audioFileId = audio.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.coverImageName) {
|
||||||
|
const cover = await prisma.fileStorage.findUnique({
|
||||||
|
where: { name: item.coverImageName },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (cover) coverImageId = cover.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.musikDesa.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
judul: item.judul,
|
||||||
|
artis: item.artis,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
durasi: item.durasi,
|
||||||
|
audioFileId,
|
||||||
|
coverImageId,
|
||||||
|
genre: item.genre,
|
||||||
|
tahunRilis: item.tahunRilis,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
judul: item.judul,
|
||||||
|
artis: item.artis,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
durasi: item.durasi,
|
||||||
|
audioFileId,
|
||||||
|
coverImageId,
|
||||||
|
genre: item.genre,
|
||||||
|
tahunRilis: item.tahunRilis,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Musik: ${item.judul} - ${item.artis}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Musik Desa seed selesai");
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import penghargaan from "../../../data/desa/penghargaan/penghargaan.json"
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const penghargaan = loadJsonData("desa/penghargaan/penghargaan.json");
|
||||||
|
|
||||||
export async function seedPenghargaan() {
|
export async function seedPenghargaan() {
|
||||||
console.log("🔄 Seeding Penghargaan...");
|
console.log("🔄 Seeding Penghargaan...");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../../load-json";
|
||||||
import { safeSeedUnique } from "../../../safeseedUnique";
|
import { safeSeedUnique } from "../../../safeseedUnique";
|
||||||
import kategoriPengumuman from "../../../data/desa/pengumuman/kategori-pengumuman.json";
|
|
||||||
import pengumuman from "../../../data/desa/pengumuman/pengumuman.json";
|
const kategoriPengumuman = loadJsonData("desa/pengumuman/kategori-pengumuman.json");
|
||||||
|
const pengumuman = loadJsonData("desa/pengumuman/pengumuman.json");
|
||||||
|
|
||||||
export async function seedPengumuman() {
|
export async function seedPengumuman() {
|
||||||
console.log("🔄 Seeding Kategori Pengumuman...");
|
console.log("🔄 Seeding Kategori Pengumuman...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kategoriPotensi from "../../../data/desa/potensi/kategori-potensi.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
import potensiDesa from "../../../data/desa/potensi/potensi-desa.json";
|
|
||||||
|
const kategoriPotensi = loadJsonData("desa/potensi/kategori-potensi.json");
|
||||||
|
const potensiDesa = loadJsonData("desa/potensi/potensi-desa.json");
|
||||||
|
|
||||||
export async function seedPotensi() {
|
export async function seedPotensi() {
|
||||||
console.log("🔄Seeding Kategori Potensi Desa ...");
|
console.log("🔄Seeding Kategori Potensi Desa ...");
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import lambangDesa from "../../../data/desa/profile/lambang_desa.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
import maskotDesa from "../../../data/desa/profile/maskot_desa.json";
|
|
||||||
import profilePerbekel from "../../../data/desa/profile/profil_perbekel.json";
|
const lambangDesa = loadJsonData("desa/profile/lambang_desa.json");
|
||||||
import profileDesaImage from "../../../data/desa/profile/profileDesaImage.json";
|
const maskotDesa = loadJsonData("desa/profile/maskot_desa.json");
|
||||||
import sejarahDesa from "../../../data/desa/profile/sejarah_desa.json";
|
const profilePerbekel = loadJsonData("desa/profile/profil_perbekel.json");
|
||||||
import visiMisiDesa from "../../../data/desa/profile/visi_misi_desa.json";
|
const profileDesaImage = loadJsonData("desa/profile/profileDesaImage.json");
|
||||||
|
const sejarahDesa = loadJsonData("desa/profile/sejarah_desa.json");
|
||||||
|
const visiMisiDesa = loadJsonData("desa/profile/visi_misi_desa.json");
|
||||||
|
|
||||||
export async function seedProfileDesa() {
|
export async function seedProfileDesa() {
|
||||||
// =========== SEJARAH DESA ===========
|
// =========== SEJARAH DESA ===========
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import perbekelDariMasaKeMasa from "../../../data/desa/profile/profile-perbekel-lalu.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const perbekelDariMasaKeMasa = loadJsonData("desa/profile/profile-perbekel-lalu.json");
|
||||||
|
|
||||||
export async function seedProfilePerbekel() {
|
export async function seedProfilePerbekel() {
|
||||||
console.log("🔄 Seeding Perbekel Dari Masa Ke Masa...");
|
console.log("🔄 Seeding Perbekel Dari Masa Ke Masa...");
|
||||||
|
|||||||
45
prisma/_seeder_list/ekonomi/seed_apbdes.ts
Normal file
45
prisma/_seeder_list/ekonomi/seed_apbdes.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const apbdesJson = loadJsonData("ekonomi/apbdes/apbdes.json");
|
||||||
|
|
||||||
|
export async function seedAPBDes() {
|
||||||
|
console.log("Seeding APBDes...");
|
||||||
|
|
||||||
|
for (const item of apbdesJson) {
|
||||||
|
let imageId: string | null = null;
|
||||||
|
let fileId: string | null = null;
|
||||||
|
|
||||||
|
if (item.imageName) {
|
||||||
|
const image = await prisma.fileStorage.findUnique({
|
||||||
|
where: { name: item.imageName },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (image) imageId = image.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.aPBDes.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
tahun: item.tahun,
|
||||||
|
name: item.name,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
imageId,
|
||||||
|
fileId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
tahun: item.tahun,
|
||||||
|
name: item.name,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
imageId,
|
||||||
|
fileId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` APBDes: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("APBDes seed selesai");
|
||||||
|
}
|
||||||
63
prisma/_seeder_list/ekonomi/seed_apbdes_item.ts
Normal file
63
prisma/_seeder_list/ekonomi/seed_apbdes_item.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const itemsJson = loadJsonData("ekonomi/apbdes/apbdes-items.json");
|
||||||
|
const realisasiJson = loadJsonData("ekonomi/apbdes/realisasi-items.json");
|
||||||
|
|
||||||
|
export async function seedAPBDesItem() {
|
||||||
|
console.log("Seeding APBDes Items...");
|
||||||
|
|
||||||
|
// Seed items first (sorted by level to ensure parents exist)
|
||||||
|
const sortedItems = [...itemsJson].sort((a, b) => a.level - b.level);
|
||||||
|
|
||||||
|
for (const item of sortedItems) {
|
||||||
|
await prisma.aPBDesItem.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
kode: item.kode,
|
||||||
|
uraian: item.uraian,
|
||||||
|
anggaran: item.anggaran,
|
||||||
|
tipe: item.tipe,
|
||||||
|
level: item.level,
|
||||||
|
parentId: item.parentId,
|
||||||
|
apbdesId: item.apbdesId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
kode: item.kode,
|
||||||
|
uraian: item.uraian,
|
||||||
|
anggaran: item.anggaran,
|
||||||
|
tipe: item.tipe,
|
||||||
|
level: item.level,
|
||||||
|
parentId: item.parentId,
|
||||||
|
apbdesId: item.apbdesId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` APBDes Item: ${item.kode} - ${item.uraian}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Realisasi Items...");
|
||||||
|
for (const item of realisasiJson) {
|
||||||
|
await prisma.realisasiItem.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
kode: item.kode,
|
||||||
|
apbdesItemId: item.apbdesItemId,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
keterangan: item.keterangan,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
kode: item.kode,
|
||||||
|
apbdesItemId: item.apbdesItemId,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
keterangan: item.keterangan,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Realisasi: ${item.kode} - Rp ${item.jumlah.toLocaleString("id-ID")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("APBDes Item & Realisasi seed selesai");
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import demografiPekerjaan from "../../data/ekonomi/demografi-pekerjaan/demografi-pekerjaan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const demografiPekerjaan = loadJsonData("ekonomi/demografi-pekerjaan/demografi-pekerjaan.json");
|
||||||
|
|
||||||
export async function seedDemografiPekerjaan() {
|
export async function seedDemografiPekerjaan() {
|
||||||
console.log("🔄 Seeding Demografi Pekerjaan...");
|
console.log("🔄 Seeding Demografi Pekerjaan...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import jumlahPendudukMiskin from "../../data/ekonomi/jumlah-penduduk-miskin/jumlah-penduduk-miskin.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const jumlahPendudukMiskin = loadJsonData("ekonomi/jumlah-penduduk-miskin/jumlah-penduduk-miskin.json");
|
||||||
|
|
||||||
export async function seedJumlahPendudukMiskin() {
|
export async function seedJumlahPendudukMiskin() {
|
||||||
console.log("🔄 Seeding Jumlah Penduduk Miskin...");
|
console.log("🔄 Seeding Jumlah Penduduk Miskin...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import jumlahPengangguran from "../../data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const jumlahPengangguran = loadJsonData("ekonomi/jumlah-pengangguran/detail-data-pengangguran.json");
|
||||||
|
|
||||||
export async function seedJumlahPengangguran() {
|
export async function seedJumlahPengangguran() {
|
||||||
for (const d of jumlahPengangguran) {
|
for (const d of jumlahPengangguran) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import lowonganKerjaLokal from "../../data/ekonomi/lowongan-kerja-lokal/lowongan-kerja-lokal.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const lowonganKerjaLokal = loadJsonData("ekonomi/lowongan-kerja-lokal/lowongan-kerja-lokal.json");
|
||||||
|
|
||||||
export async function seedLowonganKerjaLokal() {
|
export async function seedLowonganKerjaLokal() {
|
||||||
console.log("🔄 Seeding Lowongan Kerja Lokal...");
|
console.log("🔄 Seeding Lowongan Kerja Lokal...");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kategoriProduk from "../../data/ekonomi/pasar-desa/kategori-produk.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import pasarDesa from "../../data/ekonomi/pasar-desa/pasar-desa.json";
|
|
||||||
import kategoriToPasar from "../../data/ekonomi/pasar-desa/kategori-to-pasar.json";
|
const kategoriProduk = loadJsonData("ekonomi/pasar-desa/kategori-produk.json");
|
||||||
|
const pasarDesa = loadJsonData("ekonomi/pasar-desa/pasar-desa.json");
|
||||||
|
const kategoriToPasar = loadJsonData("ekonomi/pasar-desa/kategori-to-pasar.json");
|
||||||
|
|
||||||
export async function seedPasarDesa() {
|
export async function seedPasarDesa() {
|
||||||
console.log("🔄 Seeding Kategori Produk...");
|
console.log("🔄 Seeding Kategori Produk...");
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import apbdes from "../../data/ekonomi/pendapatan-asli-desa/apbDesa.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import pendapatan from "../../data/ekonomi/pendapatan-asli-desa/pendapatanDesa.json";
|
|
||||||
import belanja from "../../data/ekonomi/pendapatan-asli-desa/belanjaDesa.json";
|
const apbdes = loadJsonData("ekonomi/pendapatan-asli-desa/apbDesa.json");
|
||||||
import pembiayaan from "../../data/ekonomi/pendapatan-asli-desa/pembiayaanDesa.json";
|
const pendapatan = loadJsonData("ekonomi/pendapatan-asli-desa/pendapatanDesa.json");
|
||||||
|
const belanja = loadJsonData("ekonomi/pendapatan-asli-desa/belanjaDesa.json");
|
||||||
|
const pembiayaan = loadJsonData("ekonomi/pendapatan-asli-desa/pembiayaanDesa.json");
|
||||||
|
|
||||||
export async function seedPendapatanAsli() {
|
export async function seedPendapatanAsli() {
|
||||||
console.log("🔄 Seeding Pendapatan Asli...");
|
console.log("🔄 Seeding Pendapatan Asli...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import grafikMenganggurBerdasarkanUsia from "../../data/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran-berdasarkan-usia.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import grafikMenganggurBerdasarkanPendidikan from "../../data/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran-berdasarkan-pendidikan.json";
|
|
||||||
|
const grafikMenganggurBerdasarkanUsia = loadJsonData("ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran-berdasarkan-usia.json");
|
||||||
|
const grafikMenganggurBerdasarkanPendidikan = loadJsonData("ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran-berdasarkan-pendidikan.json");
|
||||||
|
|
||||||
export async function seedPendudukUsiaKerjaYangMenganggur() {
|
export async function seedPendudukUsiaKerjaYangMenganggur() {
|
||||||
for (const p of grafikMenganggurBerdasarkanUsia) {
|
for (const p of grafikMenganggurBerdasarkanUsia) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import programKemiskinan from "../../data/ekonomi/program-kemiskinan/program-kemiskinan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import statistikKemiskinan from "../../data/ekonomi/program-kemiskinan/statistik-kemiskinan.json";
|
|
||||||
|
const programKemiskinan = loadJsonData("ekonomi/program-kemiskinan/program-kemiskinan.json");
|
||||||
|
const statistikKemiskinan = loadJsonData("ekonomi/program-kemiskinan/statistik-kemiskinan.json");
|
||||||
|
|
||||||
export async function seedProgramKemiskinan() {
|
export async function seedProgramKemiskinan() {
|
||||||
for (const s of statistikKemiskinan) {
|
for (const s of statistikKemiskinan) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import sektorUnggulanDesa from "../../data/ekonomi/sektor-unggulan/sektor-unggulan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const sektorUnggulanDesa = loadJsonData("ekonomi/sektor-unggulan/sektor-unggulan.json");
|
||||||
|
|
||||||
export async function seedSektorUnggulanDesa() {
|
export async function seedSektorUnggulanDesa() {
|
||||||
console.log("🔄 Seeding Sektor Unggulan Desa...");
|
console.log("🔄 Seeding Sektor Unggulan Desa...");
|
||||||
|
|||||||
@@ -1,6 +1,28 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import posisiOrganisasiBumDes from "../../data/ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import pegawai from "../../data/ekonomi/struktur-organisasi/pegawai-bumdes.json";
|
|
||||||
|
interface PosisiOrganisasi {
|
||||||
|
id: string;
|
||||||
|
nama: string;
|
||||||
|
deskripsi: string;
|
||||||
|
hierarki: number;
|
||||||
|
parentId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PegawaiBumDes {
|
||||||
|
id: string;
|
||||||
|
namaLengkap: string;
|
||||||
|
gelarAkademik: string;
|
||||||
|
tanggalMasuk: string;
|
||||||
|
email: string;
|
||||||
|
telepon: string;
|
||||||
|
alamat: string;
|
||||||
|
posisiId: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const posisiOrganisasiBumDes = loadJsonData<PosisiOrganisasi[][]>("ekonomi/struktur-organisasi/posisi-organisasi-bumdes.json");
|
||||||
|
const pegawai = loadJsonData<PegawaiBumDes[]>("ekonomi/struktur-organisasi/pegawai-bumdes.json");
|
||||||
|
|
||||||
export async function seedStrukturBumdes() {
|
export async function seedStrukturBumdes() {
|
||||||
const flattenedPosisi = posisiOrganisasiBumDes.flat();
|
const flattenedPosisi = posisiOrganisasiBumDes.flat();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import ajukanIde from "../../data/inovasi/ajukan-ide/ajukan-ide.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const ajukanIde = loadJsonData("inovasi/ajukan-ide/ajukan-ide.json");
|
||||||
|
|
||||||
export async function seedAjukan() {
|
export async function seedAjukan() {
|
||||||
console.log("🔄 Seeding Ajukan Ide Inovatif...");
|
console.log("🔄 Seeding Ajukan Ide Inovatif...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import desaDigital from "../../data/inovasi/desa-digital/desa-digital.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const desaDigital = loadJsonData("inovasi/desa-digital/desa-digital.json");
|
||||||
|
|
||||||
export async function seedDesaDigital() {
|
export async function seedDesaDigital() {
|
||||||
console.log("🔄 Seeding Desa Digital...");
|
console.log("🔄 Seeding Desa Digital...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import infoTeknologi from "../../data/inovasi/info-teknologi/info-teknologi.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const infoTeknologi = loadJsonData("inovasi/info-teknologi/info-teknologi.json");
|
||||||
|
|
||||||
export async function seedInfoTeknologi() {
|
export async function seedInfoTeknologi() {
|
||||||
console.log("🔄 Seeding Info Teknologi...");
|
console.log("🔄 Seeding Info Teknologi...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kolaborasiInovasi from "../../data/inovasi/kolaborasi-inovasi/kolaborasi-inovasi.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import mitraKolaborasi from "../../data/inovasi/kolaborasi-inovasi/mitra-kolaborasi.json";
|
|
||||||
|
const kolaborasiInovasi = loadJsonData("inovasi/kolaborasi-inovasi/kolaborasi-inovasi.json");
|
||||||
|
const mitraKolaborasi = loadJsonData("inovasi/kolaborasi-inovasi/mitra-kolaborasi.json");
|
||||||
|
|
||||||
export async function seedKolaborasiInovasi() {
|
export async function seedKolaborasiInovasi() {
|
||||||
console.log("🔄 Seeding Kolaborasi Inovasi...");
|
console.log("🔄 Seeding Kolaborasi Inovasi...");
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import jenisLayanan from "../../data/inovasi/layanan-online-desa/jenis-layanan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import administrasiOnline from "../../data/inovasi/layanan-online-desa/administrasi-online.json";
|
|
||||||
import jenisPengaduan from "../../data/inovasi/layanan-online-desa/jenis-pengaduan.json";
|
const jenisLayanan = loadJsonData("inovasi/layanan-online-desa/jenis-layanan.json");
|
||||||
import pengaduanMasyarakat from "../../data/inovasi/layanan-online-desa/pengaduan-masyarakat.json";
|
const administrasiOnline = loadJsonData("inovasi/layanan-online-desa/administrasi-online.json");
|
||||||
|
const jenisPengaduan = loadJsonData("inovasi/layanan-online-desa/jenis-pengaduan.json");
|
||||||
|
const pengaduanMasyarakat = loadJsonData("inovasi/layanan-online-desa/pengaduan-masyarakat.json");
|
||||||
|
|
||||||
export async function seedLayananOnlineDesa() {
|
export async function seedLayananOnlineDesa() {
|
||||||
console.log("🔄 Seeding Jenis Layanan...");
|
console.log("🔄 Seeding Jenis Layanan...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import programKreatif from "../../data/inovasi/program-kreatif-desa/program-kreatif-desa.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const programKreatif = loadJsonData("inovasi/program-kreatif-desa/program-kreatif-desa.json");
|
||||||
|
|
||||||
export async function seedProgramKreatifDesa() {
|
export async function seedProgramKreatifDesa() {
|
||||||
console.log("🔄 Seeding Program Kreatif...");
|
console.log("🔄 Seeding Program Kreatif...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import keamananLingkunganJson from "../../data/keamanan/keamanan-lingkungan/keamanan-lingkungan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const keamananLingkunganJson = loadJsonData("keamanan/keamanan-lingkungan/keamanan-lingkungan.json");
|
||||||
|
|
||||||
export async function seedKeamananLingkungan() {
|
export async function seedKeamananLingkungan() {
|
||||||
console.log("🔄 Seeding Keamanan Lingkungan...");
|
console.log("🔄 Seeding Keamanan Lingkungan...");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kontakDaruratKeamanan from "../../data/keamanan/kontak-darurat-keamanan/kontak-darurat-keamanan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import kontakItem from "../../data/keamanan/kontak-darurat-keamanan/kontakItem.json";
|
|
||||||
import kontakDaruratToItem from "../../data/keamanan/kontak-darurat-keamanan/kontakDaruratToItem.json";
|
const kontakDaruratKeamanan = loadJsonData("keamanan/kontak-darurat-keamanan/kontak-darurat-keamanan.json");
|
||||||
|
const kontakItem = loadJsonData("keamanan/kontak-darurat-keamanan/kontakItem.json");
|
||||||
|
const kontakDaruratToItem = loadJsonData("keamanan/kontak-darurat-keamanan/kontakDaruratToItem.json");
|
||||||
|
|
||||||
export async function seedKontakDaruratKeamanan() {
|
export async function seedKontakDaruratKeamanan() {
|
||||||
console.log("🔄 Seeding Kontak Item...");
|
console.log("🔄 Seeding Kontak Item...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import laporanPublik from "../../data/keamanan/laporan-publik/laporan-publik.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import penangananLaporan from "../../data/keamanan/laporan-publik/penanganan-laporan.json";
|
|
||||||
|
const laporanPublik = loadJsonData("keamanan/laporan-publik/laporan-publik.json");
|
||||||
|
const penangananLaporan = loadJsonData("keamanan/laporan-publik/penanganan-laporan.json");
|
||||||
|
|
||||||
export async function seedLaporanPublik() {
|
export async function seedLaporanPublik() {
|
||||||
console.log("🔄 Seeding Laporan Publik...");
|
console.log("🔄 Seeding Laporan Publik...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import pencegahanKriminalitas from "../../data/keamanan/pencegahan-kriminalitas/pencegahan-kriminalitas.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const pencegahanKriminalitas = loadJsonData("keamanan/pencegahan-kriminalitas/pencegahan-kriminalitas.json");
|
||||||
|
|
||||||
export async function seedPencegahanKriminalitas() {
|
export async function seedPencegahanKriminalitas() {
|
||||||
console.log("🔄 Seeding Pencegahan Kriminalitas...");
|
console.log("🔄 Seeding Pencegahan Kriminalitas...");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import layananPolsek from "../../data/keamanan/polsek-terdekat/layanan-polsek.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import polsekTerdekat from "../../data/keamanan/polsek-terdekat/polsek-terdekat.json";
|
|
||||||
import layananToPolsek from "../../data/keamanan/polsek-terdekat/layanan-to-polsek.json";
|
const layananPolsek = loadJsonData("keamanan/polsek-terdekat/layanan-polsek.json");
|
||||||
|
const polsekTerdekat = loadJsonData("keamanan/polsek-terdekat/polsek-terdekat.json");
|
||||||
|
const layananToPolsek = loadJsonData("keamanan/polsek-terdekat/layanan-to-polsek.json");
|
||||||
|
|
||||||
export async function seedPolsekTerdekat() {
|
export async function seedPolsekTerdekat() {
|
||||||
console.log("🔄 Seeding Layanan Polsek...");
|
console.log("🔄 Seeding Layanan Polsek...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import tipsKeamananJson from "../../data/keamanan/tips-keamanan/tips-keamanan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const tipsKeamananJson = loadJsonData("keamanan/tips-keamanan/tips-keamanan.json");
|
||||||
|
|
||||||
export async function seedTipsKeamanan() {
|
export async function seedTipsKeamanan() {
|
||||||
console.log("🔄 Seeding Tips Keamanan...");
|
console.log("🔄 Seeding Tips Keamanan...");
|
||||||
|
|||||||
32
prisma/_seeder_list/kependudukan/seed_data_banjar.ts
Normal file
32
prisma/_seeder_list/kependudukan/seed_data_banjar.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const dataBanjarJson = loadJsonData("kependudukan/data-banjar/data-banjar.json");
|
||||||
|
|
||||||
|
export async function seedDataBanjar() {
|
||||||
|
console.log("Seeding Data Banjar...");
|
||||||
|
|
||||||
|
for (const item of dataBanjarJson) {
|
||||||
|
await prisma.dataBanjar.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
nama: item.nama,
|
||||||
|
penduduk: item.penduduk,
|
||||||
|
kk: item.kk,
|
||||||
|
miskin: item.miskin,
|
||||||
|
tahun: item.tahun,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
penduduk: item.penduduk,
|
||||||
|
kk: item.kk,
|
||||||
|
miskin: item.miskin,
|
||||||
|
tahun: item.tahun,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Banjar: ${item.nama} (${item.penduduk} penduduk)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Data Banjar seed selesai");
|
||||||
|
}
|
||||||
32
prisma/_seeder_list/kependudukan/seed_dinamika_penduduk.ts
Normal file
32
prisma/_seeder_list/kependudukan/seed_dinamika_penduduk.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const dinamikaPendudukJson = loadJsonData("kependudukan/dinamika-penduduk/dinamika-penduduk.json");
|
||||||
|
|
||||||
|
export async function seedDinamikaPenduduk() {
|
||||||
|
console.log("Seeding Dinamika Penduduk...");
|
||||||
|
|
||||||
|
for (const item of dinamikaPendudukJson) {
|
||||||
|
await prisma.dinamikaPenduduk.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
tahun: item.tahun,
|
||||||
|
kelahiran: item.kelahiran,
|
||||||
|
kematian: item.kematian,
|
||||||
|
masuk: item.masuk,
|
||||||
|
keluar: item.keluar,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
tahun: item.tahun,
|
||||||
|
kelahiran: item.kelahiran,
|
||||||
|
kematian: item.kematian,
|
||||||
|
masuk: item.masuk,
|
||||||
|
keluar: item.keluar,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Tahun ${item.tahun}: ${item.kelahiran} kelahiran, ${item.kematian} kematian`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Dinamika Penduduk seed selesai");
|
||||||
|
}
|
||||||
28
prisma/_seeder_list/kependudukan/seed_distribusi_agama.ts
Normal file
28
prisma/_seeder_list/kependudukan/seed_distribusi_agama.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const distribusiAgamaJson = loadJsonData("kependudukan/distribusi-agama/distribusi-agama.json");
|
||||||
|
|
||||||
|
export async function seedDistribusiAgama() {
|
||||||
|
console.log("Seeding Distribusi Agama...");
|
||||||
|
|
||||||
|
for (const item of distribusiAgamaJson) {
|
||||||
|
await prisma.distribusiAgama.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
agama: item.agama,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
tahun: item.tahun,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
agama: item.agama,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
tahun: item.tahun,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` ${item.agama}: ${item.jumlah} penganut`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Distribusi Agama seed selesai");
|
||||||
|
}
|
||||||
28
prisma/_seeder_list/kependudukan/seed_distribusi_umur.ts
Normal file
28
prisma/_seeder_list/kependudukan/seed_distribusi_umur.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const distribusiUmurJson = loadJsonData("kependudukan/distribusi-umur/distribusi-umur.json");
|
||||||
|
|
||||||
|
export async function seedDistribusiUmur() {
|
||||||
|
console.log("Seeding Distribusi Umur...");
|
||||||
|
|
||||||
|
for (const item of distribusiUmurJson) {
|
||||||
|
await prisma.distribusiUmur.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
rentangUmur: item.rentangUmur,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
tahun: item.tahun,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
rentangUmur: item.rentangUmur,
|
||||||
|
jumlah: item.jumlah,
|
||||||
|
tahun: item.tahun,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Rentang ${item.rentangUmur}: ${item.jumlah} jiwa`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Distribusi Umur seed selesai");
|
||||||
|
}
|
||||||
34
prisma/_seeder_list/kependudukan/seed_migrasi_penduduk.ts
Normal file
34
prisma/_seeder_list/kependudukan/seed_migrasi_penduduk.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const migrasiPendudukJson = loadJsonData("kependudukan/migrasi-penduduk/migrasi-penduduk.json");
|
||||||
|
|
||||||
|
export async function seedMigrasiPenduduk() {
|
||||||
|
console.log("Seeding MigrASI PENDUDUK...");
|
||||||
|
|
||||||
|
for (const item of migrasiPendudukJson) {
|
||||||
|
await prisma.migrasiPenduduk.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
nama: item.nama,
|
||||||
|
jenis: item.jenis,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
asal: item.asal,
|
||||||
|
tujuan: item.tujuan,
|
||||||
|
alasan: item.alasan,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
jenis: item.jenis,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
asal: item.asal,
|
||||||
|
tujuan: item.tujuan,
|
||||||
|
alasan: item.alasan,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` ${item.nama}: ${item.jenis} (${item.alasan})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Migrasi Penduduk seed selesai");
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const artikelJson = loadJsonData("kesehatan/artikel-kesehatan/artikel-kesehatan.json");
|
||||||
|
const introJson = loadJsonData("kesehatan/artikel-kesehatan/introduction.json");
|
||||||
|
const symptomJson = loadJsonData("kesehatan/artikel-kesehatan/symptom.json");
|
||||||
|
const preventionJson = loadJsonData("kesehatan/artikel-kesehatan/prevention.json");
|
||||||
|
const firstAidJson = loadJsonData("kesehatan/artikel-kesehatan/first-aid.json");
|
||||||
|
const mythVsFactJson = loadJsonData("kesehatan/artikel-kesehatan/myth-vs-fact.json");
|
||||||
|
const doctorSignJson = loadJsonData("kesehatan/artikel-kesehatan/doctor-sign.json");
|
||||||
|
|
||||||
|
export async function seedArtikelKesehatan() {
|
||||||
|
console.log("Seeding Introduction...");
|
||||||
|
for (const item of introJson) {
|
||||||
|
await prisma.introduction.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Symptom...");
|
||||||
|
for (const item of symptomJson) {
|
||||||
|
await prisma.symptom.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { title: item.title, content: item.content },
|
||||||
|
create: { id: item.id, title: item.title, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Prevention...");
|
||||||
|
for (const item of preventionJson) {
|
||||||
|
await prisma.prevention.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { title: item.title, content: item.content },
|
||||||
|
create: { id: item.id, title: item.title, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding First Aid...");
|
||||||
|
for (const item of firstAidJson) {
|
||||||
|
await prisma.firstAid.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { title: item.title, content: item.content },
|
||||||
|
create: { id: item.id, title: item.title, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Myth vs Fact...");
|
||||||
|
for (const item of mythVsFactJson) {
|
||||||
|
await prisma.mythVsFact.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
title: item.title,
|
||||||
|
mitos: item.mitos,
|
||||||
|
fakta: item.fakta,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
mitos: item.mitos,
|
||||||
|
fakta: item.fakta,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Doctor Sign...");
|
||||||
|
for (const item of doctorSignJson) {
|
||||||
|
await prisma.doctorSign.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Artikel Kesehatan...");
|
||||||
|
for (const item of artikelJson) {
|
||||||
|
await prisma.artikelKesehatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
title: item.title,
|
||||||
|
content: item.content,
|
||||||
|
introductionId: item.introductionId,
|
||||||
|
symptomId: item.symptomId,
|
||||||
|
preventionId: item.preventionId,
|
||||||
|
firstAidId: item.firstAidId,
|
||||||
|
mythVsFactId: item.mythVsFactId,
|
||||||
|
doctorSignId: item.doctorSignId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
content: item.content,
|
||||||
|
introductionId: item.introductionId,
|
||||||
|
symptomId: item.symptomId,
|
||||||
|
preventionId: item.preventionId,
|
||||||
|
firstAidId: item.firstAidId,
|
||||||
|
mythVsFactId: item.mythVsFactId,
|
||||||
|
doctorSignId: item.doctorSignId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Artikel: ${item.title}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Artikel Kesehatan seed selesai");
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const faskesJson = loadJsonData("kesehatan/fasilitas-kesehatan/fasilitas-kesehatan.json");
|
||||||
|
const infoUmumJson = loadJsonData("kesehatan/fasilitas-kesehatan/informasi-umum.json");
|
||||||
|
const layananUnggulanJson = loadJsonData("kesehatan/fasilitas-kesehatan/layanan-unggulan.json");
|
||||||
|
const dokterJson = loadJsonData("kesehatan/fasilitas-kesehatan/dokter-tenaga-medis.json");
|
||||||
|
const fasilitasPendukungJson = loadJsonData("kesehatan/fasilitas-kesehatan/fasilitas-pendukung.json");
|
||||||
|
const prosedurJson = loadJsonData("kesehatan/fasilitas-kesehatan/prosedur-pendaftaran.json");
|
||||||
|
const tarifJson = loadJsonData("kesehatan/fasilitas-kesehatan/tarif-layanan.json");
|
||||||
|
|
||||||
|
export async function seedFasilitasKesehatan() {
|
||||||
|
console.log("Seeding Informasi Umum...");
|
||||||
|
for (const item of infoUmumJson) {
|
||||||
|
await prisma.informasiUmum.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
fasilitas: item.fasilitas,
|
||||||
|
alamat: item.alamat,
|
||||||
|
jamOperasional: item.jamOperasional,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
fasilitas: item.fasilitas,
|
||||||
|
alamat: item.alamat,
|
||||||
|
jamOperasional: item.jamOperasional,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Informasi Umum: ${item.fasilitas}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Layanan Unggulan...");
|
||||||
|
for (const item of layananUnggulanJson) {
|
||||||
|
await prisma.layananUnggulan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Fasilitas Pendukung...");
|
||||||
|
for (const item of fasilitasPendukungJson) {
|
||||||
|
await prisma.fasilitasPendukung.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Prosedur Pendaftaran...");
|
||||||
|
for (const item of prosedurJson) {
|
||||||
|
await prisma.prosedurPendaftaran.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Tarif dan Layanan...");
|
||||||
|
for (const item of tarifJson) {
|
||||||
|
await prisma.tarifDanLayanan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { layanan: item.layanan, tarif: item.tarif },
|
||||||
|
create: { id: item.id, layanan: item.layanan, tarif: item.tarif },
|
||||||
|
});
|
||||||
|
console.log(` Tarif: ${item.layanan}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Dokter dan Tenaga Medis...");
|
||||||
|
for (const item of dokterJson) {
|
||||||
|
await prisma.dokterdanTenagaMedis.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
specialist: item.specialist,
|
||||||
|
jadwal: item.jadwal,
|
||||||
|
jadwalLibur: item.jadwalLibur,
|
||||||
|
jamBukaOperasional: item.jamBukaOperasional,
|
||||||
|
jamTutupOperasional: item.jamTutupOperasional,
|
||||||
|
jamBukaLibur: item.jamBukaLibur,
|
||||||
|
jamTutupLibur: item.jamTutupLibur,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
specialist: item.specialist,
|
||||||
|
jadwal: item.jadwal,
|
||||||
|
jadwalLibur: item.jadwalLibur,
|
||||||
|
jamBukaOperasional: item.jamBukaOperasional,
|
||||||
|
jamTutupOperasional: item.jamTutupOperasional,
|
||||||
|
jamBukaLibur: item.jamBukaLibur,
|
||||||
|
jamTutupLibur: item.jamTutupLibur,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Dokter: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Fasilitas Kesehatan...");
|
||||||
|
for (const item of faskesJson) {
|
||||||
|
await prisma.fasilitasKesehatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
informasiUmumId: item.informasiUmumId,
|
||||||
|
layananUnggulanId: item.layananUnggulanId,
|
||||||
|
fasilitasPendukungId: item.fasilitasPendukungId,
|
||||||
|
prosedurPendaftaranId: item.prosedurPendaftaranId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
informasiUmumId: item.informasiUmumId,
|
||||||
|
layananUnggulanId: item.layananUnggulanId,
|
||||||
|
fasilitasPendukungId: item.fasilitasPendukungId,
|
||||||
|
prosedurPendaftaranId: item.prosedurPendaftaranId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Fasilitas Kesehatan: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Fasilitas Kesehatan seed selesai");
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import infoWabahPenyakitJson from "../../../data/kesehatan/infowabahpenyakit/infowabahpenyakit.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const infoWabahPenyakitJson = loadJsonData("kesehatan/infowabahpenyakit/infowabahpenyakit.json");
|
||||||
|
|
||||||
export async function seedInfoWabahPenyakit() {
|
export async function seedInfoWabahPenyakit() {
|
||||||
console.log("🔄 Seeding Info Wabah Penyakit...");
|
console.log("🔄 Seeding Info Wabah Penyakit...");
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const jadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/jadwal-kegiatan.json");
|
||||||
|
const infoJadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/informasi-jadwal.json");
|
||||||
|
const deskJadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/deskripsi-jadwal.json");
|
||||||
|
const layananJadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/layanan-jadwal.json");
|
||||||
|
const syaratJadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/syarat-ketentuan.json");
|
||||||
|
const dokumenJadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/dokumen-jadwal.json");
|
||||||
|
const daftarJadwalJson = loadJsonData("kesehatan/jadwal-kegiatan/pendaftaran-jadwal.json");
|
||||||
|
|
||||||
|
export async function seedJadwalKegiatan() {
|
||||||
|
console.log("Seeding Informasi Jadwal Kegiatan...");
|
||||||
|
for (const item of infoJadwalJson) {
|
||||||
|
await prisma.informasiJadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
tanggal: item.tanggal,
|
||||||
|
waktu: item.waktu,
|
||||||
|
lokasi: item.lokasi,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
tanggal: item.tanggal,
|
||||||
|
waktu: item.waktu,
|
||||||
|
lokasi: item.lokasi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Informasi: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Deskripsi Jadwal Kegiatan...");
|
||||||
|
for (const item of deskJadwalJson) {
|
||||||
|
await prisma.deskripsiJadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { deskripsi: item.deskripsi },
|
||||||
|
create: { id: item.id, deskripsi: item.deskripsi },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Layanan Jadwal Kegiatan...");
|
||||||
|
for (const item of layananJadwalJson) {
|
||||||
|
await prisma.layananJadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Syarat & Ketentuan Jadwal...");
|
||||||
|
for (const item of syaratJadwalJson) {
|
||||||
|
await prisma.syaratKetentuanJadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Dokumen Jadwal Kegiatan...");
|
||||||
|
for (const item of dokumenJadwalJson) {
|
||||||
|
await prisma.dokumenJadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: { content: item.content },
|
||||||
|
create: { id: item.id, content: item.content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Pendaftaran Jadwal Kegiatan...");
|
||||||
|
for (const item of daftarJadwalJson) {
|
||||||
|
await prisma.pendaftaranJadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
name: item.name,
|
||||||
|
tanggal: item.tanggal,
|
||||||
|
namaOrangtua: item.namaOrtu,
|
||||||
|
nomor: item.nomor,
|
||||||
|
alamat: item.alamat,
|
||||||
|
catatan: item.catatan,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
tanggal: item.tanggal,
|
||||||
|
namaOrangtua: item.namaOrtu,
|
||||||
|
nomor: item.nomor,
|
||||||
|
alamat: item.alamat,
|
||||||
|
catatan: item.catatan,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Pendaftaran: ${item.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Jadwal Kegiatan...");
|
||||||
|
for (const item of jadwalJson) {
|
||||||
|
await prisma.jadwalKegiatan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
content: item.content,
|
||||||
|
informasiJadwalKegiatanId: item.informasiJadwalKegiatanId,
|
||||||
|
deskripsiJadwalKegiatanId: item.deskripsiJadwalKegiatanId,
|
||||||
|
layananJadwalKegiatanId: item.layananJadwalKegiatanId,
|
||||||
|
syaratKetentuanJadwalKegiatanId: item.syaratKetentuanJadwalKegiatanId,
|
||||||
|
dokumenJadwalKegiatanId: item.dokumenJadwalKegiatanId,
|
||||||
|
pendaftaranJadwalKegiatanId: item.pendaftaranJadwalKegiatanId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
content: item.content,
|
||||||
|
informasiJadwalKegiatanId: item.informasiJadwalKegiatanId,
|
||||||
|
deskripsiJadwalKegiatanId: item.deskripsiJadwalKegiatanId,
|
||||||
|
layananJadwalKegiatanId: item.layananJadwalKegiatanId,
|
||||||
|
syaratKetentuanJadwalKegiatanId: item.syaratKetentuanJadwalKegiatanId,
|
||||||
|
dokumenJadwalKegiatanId: item.dokumenJadwalKegiatanId,
|
||||||
|
pendaftaranJadwalKegiatanId: item.pendaftaranJadwalKegiatanId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Jadwal Kegiatan seeded`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Jadwal Kegiatan seed selesai");
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import kontakDaruratJson from "../../../data/kesehatan/kontak-darurat/kontak-darurat.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
const kontakDaruratJson = loadJsonData("kesehatan/kontak-darurat/kontak-darurat.json");
|
||||||
|
|
||||||
export async function seedKontakDarurat() {
|
export async function seedKontakDarurat() {
|
||||||
console.log("🔄 Seeding Kontak Darurat...");
|
console.log("🔄 Seeding Kontak Darurat...");
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import penangananDaruratJson from "../../../data/kesehatan/penanganan-darurat/penganan-darurat.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const penangananDaruratJson = loadJsonData("kesehatan/penanganan-darurat/penganan-darurat.json");
|
||||||
|
|
||||||
export async function seedPenangananDarurat() {
|
export async function seedPenangananDarurat() {
|
||||||
console.log("🔄 Seeding Penanganan Darurat...");
|
console.log("🔄 Seeding Penanganan Darurat...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import posyanduJson from "../../../data/kesehatan/posyandu/posyandu.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const posyanduJson = loadJsonData("kesehatan/posyandu/posyandu.json");
|
||||||
|
|
||||||
export async function seedPosyandu() {
|
export async function seedPosyandu() {
|
||||||
console.log("🔄 Seeding Posyandu...");
|
console.log("🔄 Seeding Posyandu...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import programKesehatanJson from "../../../data/kesehatan/program-kesehatan/program-kesehatan.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const programKesehatanJson = loadJsonData("kesehatan/program-kesehatan/program-kesehatan.json");
|
||||||
|
|
||||||
export async function seedProgramKesehatan() {
|
export async function seedProgramKesehatan() {
|
||||||
for (const p of programKesehatanJson) {
|
for (const p of programKesehatanJson) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import puskesmasJson from "../../../data/kesehatan/puskesmas/puskesmas.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
import kontakPuskesmasJson from "../../../data/kesehatan/puskesmas/kontak-puskesmas/kontak.json";
|
|
||||||
import jamPuskesmasJson from "../../../data/kesehatan/puskesmas/jam-puskesmas/jam.json";
|
const puskesmasJson = loadJsonData("kesehatan/puskesmas/puskesmas.json");
|
||||||
|
const kontakPuskesmasJson = loadJsonData("kesehatan/puskesmas/kontak-puskesmas/kontak.json");
|
||||||
|
const jamPuskesmasJson = loadJsonData("kesehatan/puskesmas/jam-puskesmas/jam.json");
|
||||||
|
|
||||||
export async function seedPuskesmas() {
|
export async function seedPuskesmas() {
|
||||||
console.log("🔄 Seeding Kontak Puskesmas...");
|
console.log("🔄 Seeding Kontak Puskesmas...");
|
||||||
|
|||||||
32
prisma/_seeder_list/kesehatan/seed_grafik_kepuasan.ts
Normal file
32
prisma/_seeder_list/kesehatan/seed_grafik_kepuasan.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const grafikKepuasanJson = loadJsonData("kesehatan/grafik-kepuasan/grafik-kepuasan.json");
|
||||||
|
|
||||||
|
export async function seedGrafikKepuasan() {
|
||||||
|
console.log("Seeding Grafik Kepuasan...");
|
||||||
|
|
||||||
|
for (const item of grafikKepuasanJson) {
|
||||||
|
await prisma.grafikKepuasan.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
penyakit: item.penyakit,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
penyakit: item.penyakit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Grafik Kepuasan: ${item.nama}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Grafik Kepuasan seed selesai");
|
||||||
|
}
|
||||||
70
prisma/_seeder_list/kesehatan/seed_kelahiran_kematian.ts
Normal file
70
prisma/_seeder_list/kesehatan/seed_kelahiran_kematian.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const kelahiranJson = loadJsonData("kesehatan/kelahiran/kelahiran.json");
|
||||||
|
const kematianJson = loadJsonData("kesehatan/kematian/kematian.json");
|
||||||
|
const dataKematianKelahiranJson = loadJsonData("kesehatan/kematian-kelahiran/data-kematian-kelahiran.json");
|
||||||
|
|
||||||
|
export async function seedKelahiranKematian() {
|
||||||
|
console.log("Seeding Kelahiran...");
|
||||||
|
for (const item of kelahiranJson) {
|
||||||
|
await prisma.kelahiran.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Kelahiran: ${item.nama}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Kematian...");
|
||||||
|
for (const item of kematianJson) {
|
||||||
|
await prisma.kematian.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
penyebab: item.penyebab,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
nama: item.nama,
|
||||||
|
tanggal: new Date(item.tanggal),
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamat: item.alamat,
|
||||||
|
penyebab: item.penyebab,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Kematian: ${item.nama}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seeding Data Kematian-Kelahiran...");
|
||||||
|
for (const item of dataKematianKelahiranJson) {
|
||||||
|
await prisma.dataKematian_Kelahiran.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
kematianId: item.kematianId,
|
||||||
|
kelahiranId: item.kelahiranId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
kematianId: item.kematianId,
|
||||||
|
kelahiranId: item.kelahiranId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Kelahiran & Kematian seed selesai");
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kategoriDesaAntiKorupsi from "../../../data/landing-page/desa-anti-korupsi/kategoriDesaAntiKorupsi.json"
|
import { loadJsonData } from "../../../load-json";
|
||||||
import desaAntiKorupsi from "../../../data/landing-page/desa-anti-korupsi/desaantiKorpusi.json"
|
|
||||||
|
const kategoriDesaAntiKorupsi = loadJsonData("landing-page/desa-anti-korupsi/kategoriDesaAntiKorupsi.json");
|
||||||
|
const desaAntiKorupsi = loadJsonData("landing-page/desa-anti-korupsi/desaantiKorpusi.json");
|
||||||
|
|
||||||
export async function seedDesaAntiKorupsi() {
|
export async function seedDesaAntiKorupsi() {
|
||||||
for (const k of kategoriDesaAntiKorupsi) {
|
for (const k of kategoriDesaAntiKorupsi) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import prestasiDesa from "../../../data/landing-page/prestasi-desa/prestasi-desa.json"
|
import { loadJsonData } from "../../../load-json";
|
||||||
import kategoriPrestasiDesa from "../../../data/landing-page/prestasi-desa/kategori-prestasi.json"
|
|
||||||
|
const prestasiDesa = loadJsonData("landing-page/prestasi-desa/prestasi-desa.json");
|
||||||
|
const kategoriPrestasiDesa = loadJsonData("landing-page/prestasi-desa/kategori-prestasi.json");
|
||||||
|
|
||||||
export async function seedPrestasiDesa() {
|
export async function seedPrestasiDesa() {
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import mediaSosial from "../../../data/landing-page/profile/mediaSosial.json"
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const mediaSosial = loadJsonData("landing-page/profile/mediaSosial.json");
|
||||||
|
|
||||||
export async function seedMediaSosial() {
|
export async function seedMediaSosial() {
|
||||||
console.log("🔄 Seeding Media Sosial...");
|
console.log("🔄 Seeding Media Sosial...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import profilePejabatDesa from "../../../data/landing-page/profile/profile.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const profilePejabatDesa = loadJsonData("landing-page/profile/profile.json");
|
||||||
|
|
||||||
export async function seedProfileLP() {
|
export async function seedProfileLP() {
|
||||||
console.log("🔄 Seeding Pejabat Desa...");
|
console.log("🔄 Seeding Pejabat Desa...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import programInovasi from "../../../data/landing-page/profile/programInovasi.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const programInovasi = loadJsonData("landing-page/profile/programInovasi.json");
|
||||||
|
|
||||||
export async function seedProgramInovasi() {
|
export async function seedProgramInovasi() {
|
||||||
console.log("🔄 Seeding Program Inovasi...");
|
console.log("🔄 Seeding Program Inovasi...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import sdgsDesa from "../../../data/landing-page/sdgs-desa/sdgs-desa.json";
|
import { loadJsonData } from "../../../load-json";
|
||||||
|
|
||||||
|
const sdgsDesa = loadJsonData("landing-page/sdgs-desa/sdgs-desa.json");
|
||||||
|
|
||||||
export async function seedSDGSDesa() {
|
export async function seedSDGSDesa() {
|
||||||
console.log("🔄 Seeding SDGS Desa...");
|
console.log("🔄 Seeding SDGS Desa...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import kategoriGotongRoyong from "../../data/lingkungan/gotong-royong/kategori-gotong-royong.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import gotongRoyong from "../../data/lingkungan/gotong-royong/gotong-royong.json";
|
|
||||||
|
const kategoriGotongRoyong = loadJsonData("lingkungan/gotong-royong/kategori-gotong-royong.json");
|
||||||
|
const gotongRoyong = loadJsonData("lingkungan/gotong-royong/gotong-royong.json");
|
||||||
|
|
||||||
export async function seedDataGotongRoyong() {
|
export async function seedDataGotongRoyong() {
|
||||||
console.log("🔄 Seeding Kategori Gotong Royong...");
|
console.log("🔄 Seeding Kategori Gotong Royong...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import dataLingkunganDesa from "../../data/lingkungan/data-lingkungan-desa/data-lingkungan-desa.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const dataLingkunganDesa = loadJsonData("lingkungan/data-lingkungan-desa/data-lingkungan-desa.json");
|
||||||
|
|
||||||
export async function seedDataLingkunganDesa() {
|
export async function seedDataLingkunganDesa() {
|
||||||
console.log("🔄 Seeding Data Lingkungan Desa...");
|
console.log("🔄 Seeding Data Lingkungan Desa...");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import tujuanEdukasiLingkungan from "../../data/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import materiEdukasiLingkungan from "../../data/lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json";
|
|
||||||
import contohEdukasiLingkungan from "../../data/lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json";
|
const tujuanEdukasiLingkungan = loadJsonData("lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan.json");
|
||||||
|
const materiEdukasiLingkungan = loadJsonData("lingkungan/edukasi-lingkungan/materi-edukasi-yang-diberikan.json");
|
||||||
|
const contohEdukasiLingkungan = loadJsonData("lingkungan/edukasi-lingkungan/contoh-kegiatan-di-desa-darmasaba.json");
|
||||||
|
|
||||||
export async function seedEdukasiLingkungan() {
|
export async function seedEdukasiLingkungan() {
|
||||||
for (const e of tujuanEdukasiLingkungan) {
|
for (const e of tujuanEdukasiLingkungan) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import filosofiTriHita from "../../data/lingkungan/konservasi-adat-bali/filosofi-tri-hita.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import bentukKonservasiBerdasarkanAdat from "../../data/lingkungan/konservasi-adat-bali/bentuk-konservasi.json";
|
|
||||||
import nilaiKonservasiAdat from "../../data/lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json";
|
const filosofiTriHita = loadJsonData("lingkungan/konservasi-adat-bali/filosofi-tri-hita.json");
|
||||||
|
const bentukKonservasiBerdasarkanAdat = loadJsonData("lingkungan/konservasi-adat-bali/bentuk-konservasi.json");
|
||||||
|
const nilaiKonservasiAdat = loadJsonData("lingkungan/konservasi-adat-bali/nilai-konservasi-adat.json");
|
||||||
|
|
||||||
export async function seedKonservasiAdatBali() {
|
export async function seedKonservasiAdatBali() {
|
||||||
for (const f of filosofiTriHita) {
|
for (const f of filosofiTriHita) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import pengelolaanSampah from "../../data/lingkungan/pengelolaan-sampah/pengelolaan-sampah.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import keteranganBankSampah from "../../data/lingkungan/pengelolaan-sampah/keterangan-bank-sampah.json";
|
|
||||||
|
const pengelolaanSampah = loadJsonData("lingkungan/pengelolaan-sampah/pengelolaan-sampah.json");
|
||||||
|
const keteranganBankSampah = loadJsonData("lingkungan/pengelolaan-sampah/keterangan-bank-sampah.json");
|
||||||
|
|
||||||
export async function seedPengelolaanSampah() {
|
export async function seedPengelolaanSampah() {
|
||||||
console.log("🔄 Seeding Pengelolaan Sampah...");
|
console.log("🔄 Seeding Pengelolaan Sampah...");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import programPenghijauan from "../../data/lingkungan/program-penghijauan/program-penghijauan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const programPenghijauan = loadJsonData("lingkungan/program-penghijauan/program-penghijauan.json");
|
||||||
|
|
||||||
export async function seedProgramPenghijauan() {
|
export async function seedProgramPenghijauan() {
|
||||||
console.log("🔄 Seeding Program Penghijauan...");
|
console.log("🔄 Seeding Program Penghijauan...");
|
||||||
|
|||||||
58
prisma/_seeder_list/pendidikan/seed_beasiswa_pendaftar.ts
Normal file
58
prisma/_seeder_list/pendidikan/seed_beasiswa_pendaftar.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const beasiswaJson = loadJsonData("pendidikan/beasiswa-pendaftar/beasiswa-pendaftar.json");
|
||||||
|
|
||||||
|
export async function seedBeasiswaPendaftar() {
|
||||||
|
console.log("Seeding Beasiswa Pendaftar...");
|
||||||
|
|
||||||
|
for (const item of beasiswaJson) {
|
||||||
|
await prisma.beasiswaPendaftar.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
namaLengkap: item.namaLengkap,
|
||||||
|
nis: item.nis,
|
||||||
|
kelas: item.kelas,
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamatDomisili: item.alamatDomisili,
|
||||||
|
tempatLahir: item.tempatLahir,
|
||||||
|
tanggalLahir: new Date(item.tanggalLahir),
|
||||||
|
namaOrtu: item.namaOrtu,
|
||||||
|
nik: item.nik,
|
||||||
|
pekerjaanOrtu: item.pekerjaanOrtu,
|
||||||
|
penghasilan: item.penghasilan,
|
||||||
|
noHp: item.noHp,
|
||||||
|
kewarganegaraan: item.kewarganegaraan,
|
||||||
|
agama: item.agama,
|
||||||
|
alamatKTP: item.alamatKTP,
|
||||||
|
email: item.email,
|
||||||
|
statusPernikahan: item.statusPernikahan,
|
||||||
|
ukuranBaju: item.ukuranBaju,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
namaLengkap: item.namaLengkap,
|
||||||
|
nis: item.nis,
|
||||||
|
kelas: item.kelas,
|
||||||
|
jenisKelamin: item.jenisKelamin,
|
||||||
|
alamatDomisili: item.alamatDomisili,
|
||||||
|
tempatLahir: item.tempatLahir,
|
||||||
|
tanggalLahir: new Date(item.tanggalLahir),
|
||||||
|
namaOrtu: item.namaOrtu,
|
||||||
|
nik: item.nik,
|
||||||
|
pekerjaanOrtu: item.pekerjaanOrtu,
|
||||||
|
penghasilan: item.penghasilan,
|
||||||
|
noHp: item.noHp,
|
||||||
|
kewarganegaraan: item.kewarganegaraan,
|
||||||
|
agama: item.agama,
|
||||||
|
alamatKTP: item.alamatKTP,
|
||||||
|
email: item.email,
|
||||||
|
statusPernikahan: item.statusPernikahan,
|
||||||
|
ukuranBaju: item.ukuranBaju,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Beasiswa: ${item.namaLengkap} (NIS: ${item.nis})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Beasiswa Pendaftar seed selesai");
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import tujuanBimbinganBelajarDesa from "../../data/pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import lokasiJadwalBimbinganBelajarDesa from "../../data/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json";
|
|
||||||
import fasilitasBimbinganBelajarDesa from "../../data/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json";
|
const tujuanBimbinganBelajarDesa = loadJsonData("pendidikan/bimbingan-belajar-desa/tujuan-bimbingan-belajar-desa.json");
|
||||||
|
const lokasiJadwalBimbinganBelajarDesa = loadJsonData("pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal.json");
|
||||||
|
const fasilitasBimbinganBelajarDesa = loadJsonData("pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan.json");
|
||||||
|
|
||||||
export async function seedBimbinganBelajar() {
|
export async function seedBimbinganBelajar() {
|
||||||
for (const t of tujuanBimbinganBelajarDesa) {
|
for (const t of tujuanBimbinganBelajarDesa) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import dataPendidikan from "../../data/pendidikan/data-pendidikan/data-pendidikan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
|
|
||||||
|
const dataPendidikan = loadJsonData("pendidikan/data-pendidikan/data-pendidikan.json");
|
||||||
|
|
||||||
export async function seedDataPendidikan() {
|
export async function seedDataPendidikan() {
|
||||||
console.log("🔄 Seeding Data pendidikan...");
|
console.log("🔄 Seeding Data pendidikan...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import dataPerpustakaan from "../../data/pendidikan/perpustakaan-digital/perpustakaan-digital.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import kategoriBuku from "../../data/pendidikan/perpustakaan-digital/kategori-buku.json";
|
|
||||||
|
const dataPerpustakaan = loadJsonData("pendidikan/perpustakaan-digital/perpustakaan-digital.json");
|
||||||
|
const kategoriBuku = loadJsonData("pendidikan/perpustakaan-digital/kategori-buku.json");
|
||||||
|
|
||||||
export async function seedDataPerpustakaan() {
|
export async function seedDataPerpustakaan() {
|
||||||
console.log("🔄 Seeding Kategori Buku...");
|
console.log("🔄 Seeding Kategori Buku...");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import tujuanProgram from "../../data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import programUnggulan from "../../data/pendidikan/program-pendidikan-anak/program-unggulan.json";
|
|
||||||
|
const tujuanProgram = loadJsonData("pendidikan/program-pendidikan-anak/tujuan-program.json");
|
||||||
|
const programUnggulan = loadJsonData("pendidikan/program-pendidikan-anak/program-unggulan.json");
|
||||||
|
|
||||||
export async function seedInfoProgramPendidikan() {
|
export async function seedInfoProgramPendidikan() {
|
||||||
for (const t of tujuanProgram) {
|
for (const t of tujuanProgram) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import jenjangPendidikan from "../../data/pendidikan/info-sekolah/jenjang-pendidikan.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import lembagaPendidikan from "../../data/pendidikan/info-sekolah/lembaga.json";
|
|
||||||
import siswa from "../../data/pendidikan/info-sekolah/siswa.json";
|
const jenjangPendidikan = loadJsonData("pendidikan/info-sekolah/jenjang-pendidikan.json");
|
||||||
import pengajar from "../../data/pendidikan/info-sekolah/pengajar.json";
|
const lembagaPendidikan = loadJsonData("pendidikan/info-sekolah/lembaga.json");
|
||||||
|
const siswa = loadJsonData("pendidikan/info-sekolah/siswa.json");
|
||||||
|
const pengajar = loadJsonData("pendidikan/info-sekolah/pengajar.json");
|
||||||
|
|
||||||
export async function seedInfoSekolah() {
|
export async function seedInfoSekolah() {
|
||||||
for (const j of jenjangPendidikan) {
|
for (const j of jenjangPendidikan) {
|
||||||
|
|||||||
32
prisma/_seeder_list/pendidikan/seed_keunggulan_program.ts
Normal file
32
prisma/_seeder_list/pendidikan/seed_keunggulan_program.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
const keunggulanJson = JSON.parse(
|
||||||
|
readFileSync(
|
||||||
|
join(process.cwd(), "prisma/data/pendidikan/keunggulan-program/keunggulan-program.json"),
|
||||||
|
"utf-8"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export async function seedKeunggulanProgram() {
|
||||||
|
console.log("Seeding Keunggulan Program...");
|
||||||
|
|
||||||
|
for (const item of keunggulanJson) {
|
||||||
|
await prisma.keunggulanProgram.upsert({
|
||||||
|
where: { id: item.id },
|
||||||
|
update: {
|
||||||
|
judul: item.judul,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
id: item.id,
|
||||||
|
judul: item.judul,
|
||||||
|
deskripsi: item.deskripsi,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log(` Keunggulan: ${item.judul}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Keunggulan Program seed selesai");
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import tujuanProgram from "../../data/pendidikan/pendidikan-non-formal/tujuan-program2.json";
|
import { loadJsonData } from "../../load-json";
|
||||||
import tempatKegiatan from "../../data/pendidikan/pendidikan-non-formal/tempat-kegiatan.json";
|
|
||||||
import jenisProgramYangDiselenggarakan from "../../data/pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json";
|
const tujuanProgram = loadJsonData("pendidikan/pendidikan-non-formal/tujuan-program2.json");
|
||||||
|
const tempatKegiatan = loadJsonData("pendidikan/pendidikan-non-formal/tempat-kegiatan.json");
|
||||||
|
const jenisProgramYangDiselenggarakan = loadJsonData("pendidikan/pendidikan-non-formal/jenis-program-yang-diselenggarakan.json");
|
||||||
|
|
||||||
export async function seedPendidikanNonFormal() {
|
export async function seedPendidikanNonFormal() {
|
||||||
for (const t of tujuanProgram) {
|
for (const t of tujuanProgram) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import daftarInformasiPublik from "../../../data/ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json"
|
import { loadJsonData } from "../../../load-json";
|
||||||
import jenisInformasiDiminta from "../../../data/list-jenisInfromasi.json"
|
|
||||||
import caraMemperolehInformasi from "../../../data/list-caraMemperolehInformasi.json"
|
const daftarInformasiPublik = loadJsonData("ppid/daftar-informasi-publik-desa-darmasaba/daftarInformasi.json");
|
||||||
import caraMemperolehSalinanInformasi from "../../../data/list-caraMemperolehSalinanInformasi.json"
|
const jenisInformasiDiminta = loadJsonData("list-jenisInfromasi.json");
|
||||||
|
const caraMemperolehInformasi = loadJsonData("list-caraMemperolehInformasi.json");
|
||||||
|
const caraMemperolehSalinanInformasi = loadJsonData("list-caraMemperolehSalinanInformasi.json");
|
||||||
|
|
||||||
export async function seedDaftarInformasiPublikPpid() {
|
export async function seedDaftarInformasiPublikPpid() {
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user