Compare commits
8 Commits
0806eb2308
...
nico/2-apr
| Author | SHA1 | Date | |
|---|---|---|---|
| 721357adcf | |||
| 50a7356618 | |||
| 970949a68b | |||
| 8777c45a44 | |||
| b751f031cd | |||
| a3940321a7 | |||
| 3cd6fcbd81 | |||
| 7d9b7b0c60 |
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
|
||||
4
.env
4
.env
@@ -15,5 +15,5 @@ BASE_SESSION_KEY=kp9sGx91as0Kj2Ls81nAsl2Kdj13KsxP
|
||||
BASE_TOKEN_KEY=Qm82JsA92lMnKw0291mxKaaP02KjslaA
|
||||
|
||||
# BOT-TELE
|
||||
BOT_TOKEN=8498428675:AAEQwAUjTqpvgyyC5C123nP1mAxhOg12Ph0
|
||||
CHAT_ID=5251328671
|
||||
BOT_TOKEN=8479423145:AAE9ArrOgTD3DyVxYSVs3IXN40u_sL6c9sw
|
||||
CHAT_ID=-1003368982298
|
||||
@@ -1,43 +1,52 @@
|
||||
#!/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 findLongestString(obj: any): string {
|
||||
let longest = "";
|
||||
const search = (item: any) => {
|
||||
if (typeof item === "string") {
|
||||
if (item.length > longest.length) longest = item;
|
||||
} else if (Array.isArray(item)) {
|
||||
item.forEach(search);
|
||||
} else if (item && typeof item === "object") {
|
||||
Object.values(item).forEach(search);
|
||||
// Function to manually load .env from project root if process.env is missing keys
|
||||
function loadEnv() {
|
||||
const envPath = join(process.cwd(), ".env");
|
||||
if (existsSync(envPath)) {
|
||||
const envContent = readFileSync(envPath, "utf-8");
|
||||
const lines = envContent.split("\n");
|
||||
for (const line of lines) {
|
||||
if (line && !line.startsWith("#")) {
|
||||
const [key, ...valueParts] = line.split("=");
|
||||
if (key && valueParts.length > 0) {
|
||||
const value = valueParts.join("=").trim().replace(/^["']|["']$/g, "");
|
||||
process.env[key.trim()] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
search(obj);
|
||||
return longest;
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
// Ensure environment variables are loaded
|
||||
loadEnv();
|
||||
|
||||
const inputRaw = readFileSync(0, "utf-8");
|
||||
if (!inputRaw) return;
|
||||
const input = JSON.parse(inputRaw);
|
||||
|
||||
// DEBUG: Lihat struktur asli di console terminal (stderr)
|
||||
console.error("DEBUG KEYS:", Object.keys(input));
|
||||
let finalText = "";
|
||||
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 CHAT_ID = process.env.CHAT_ID;
|
||||
|
||||
const sessionId = input.session_id || "unknown";
|
||||
|
||||
// Cari teks secara otomatis di seluruh objek JSON
|
||||
let finalText = findLongestString(input.response || input);
|
||||
|
||||
if (!finalText || finalText.length < 5) {
|
||||
finalText =
|
||||
"Teks masih gagal diekstraksi. Struktur: " +
|
||||
Object.keys(input).join(", ");
|
||||
if (!BOT_TOKEN || !CHAT_ID) {
|
||||
console.error("Missing BOT_TOKEN or CHAT_ID in environment variables");
|
||||
return;
|
||||
}
|
||||
|
||||
const message =
|
||||
@@ -45,7 +54,7 @@ async function run() {
|
||||
`🆔 Session: \`${sessionId}\` \n\n` +
|
||||
`🧠 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",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
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" }));
|
||||
} catch (err) {
|
||||
console.error("Hook Error:", err);
|
||||
|
||||
84
Dockerfile
84
Dockerfile
@@ -1,3 +1,77 @@
|
||||
# # Stage 1: Build
|
||||
# FROM oven/bun:1.1 AS build
|
||||
|
||||
# # Install build dependencies for native modules
|
||||
# RUN apt-get update && apt-get install -y \
|
||||
# python3 \
|
||||
# make \
|
||||
# g++ \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# # Set the working directory
|
||||
# WORKDIR /app
|
||||
|
||||
# # Disable telemetry and set build-time environment
|
||||
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# ENV NODE_ENV=production
|
||||
# ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
# # Critical ENV for API route evaluation during build
|
||||
# ENV WIBU_UPLOAD_DIR=uploads
|
||||
# ENV DATABASE_URL="postgresql://bip:Production_123@pgbouncer:5432/desa-darmasaba-staging?pgbouncer=true"
|
||||
|
||||
# # Copy package files
|
||||
# COPY package.json bun.lock* ./
|
||||
|
||||
# # Install dependencies with frozen lockfile
|
||||
# RUN bun install --frozen-lockfile
|
||||
|
||||
# # Copy the rest of the application code
|
||||
# COPY . .
|
||||
|
||||
# # Use .env.example as default env for build
|
||||
# RUN cp .env.example .env
|
||||
|
||||
# # Generate Prisma client
|
||||
# RUN bun x prisma generate
|
||||
|
||||
# # Build the application frontend
|
||||
# RUN bun run build
|
||||
|
||||
# # Stage 2: Runtime
|
||||
# FROM oven/bun:1.1-slim AS runtime
|
||||
|
||||
# # Set environment variables
|
||||
# ENV NODE_ENV=production
|
||||
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# # Ensure runtime also has critical envs if they are checked at startup
|
||||
# ENV WIBU_UPLOAD_DIR=uploads
|
||||
|
||||
# # Install runtime dependencies
|
||||
# RUN apt-get update && apt-get install -y \
|
||||
# postgresql-client \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# # Set the working directory
|
||||
# WORKDIR /app
|
||||
|
||||
# # Copy necessary files from build stage
|
||||
# COPY --from=build /app/package.json ./
|
||||
# COPY --from=build /app/bun.lock* ./
|
||||
# COPY --from=build /app/next.config.ts ./
|
||||
# COPY --from=build /app/postcss.config.cjs ./
|
||||
# COPY --from=build /app/tsconfig.json ./
|
||||
# COPY --from=build /app/.next ./.next
|
||||
# COPY --from=build /app/public ./public
|
||||
# COPY --from=build /app/node_modules ./node_modules
|
||||
# COPY --from=build /app/prisma ./prisma
|
||||
|
||||
# # Expose the port
|
||||
# EXPOSE 3000
|
||||
|
||||
# # Start the application
|
||||
# CMD ["bun", "start"]
|
||||
|
||||
|
||||
# Stage 1: Build
|
||||
FROM oven/bun:1.3 AS build
|
||||
|
||||
@@ -26,8 +100,10 @@ RUN cp .env.example .env
|
||||
# Generate Prisma client
|
||||
RUN bun x prisma generate
|
||||
|
||||
# Generate API types
|
||||
RUN bun run gen:api
|
||||
|
||||
# Build the application frontend
|
||||
ENV NODE_ENV=production
|
||||
RUN bun run build
|
||||
|
||||
# Stage 2: Runtime
|
||||
@@ -47,8 +123,8 @@ WORKDIR /app
|
||||
# Copy necessary files from build stage
|
||||
COPY --from=build /app/package.json ./
|
||||
COPY --from=build /app/tsconfig.json ./
|
||||
COPY --from=build /app/.next ./.next
|
||||
COPY --from=build /app/public ./public
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY --from=build /app/generated ./generated
|
||||
COPY --from=build /app/src ./src
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/prisma ./prisma
|
||||
@@ -57,4 +133,4 @@ COPY --from=build /app/prisma ./prisma
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the application
|
||||
CMD ["bun", "start"]
|
||||
CMD ["bun", "start"]
|
||||
@@ -120,7 +120,7 @@
|
||||
"@types/react-dom": "^19",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.6",
|
||||
"eslint-config-next": "15.5.12",
|
||||
"jsdom": "^28.0.0",
|
||||
"msw": "^2.12.9",
|
||||
"parcel": "^2.6.2",
|
||||
|
||||
@@ -15,7 +15,7 @@ import AjukanPermohonan from "./layanan/ajukan_permohonan";
|
||||
import Musik from "./musik";
|
||||
|
||||
|
||||
const Desa = new Elysia({ prefix: "/api/desa", tags: ["Desa"] })
|
||||
const Desa = new Elysia({ prefix: "/desa", tags: ["Desa"] })
|
||||
.use(Berita)
|
||||
.use(Pengumuman)
|
||||
.use(ProfileDesa)
|
||||
|
||||
@@ -13,7 +13,7 @@ import PendapatanAsliDesa from "./pendapatan-asli-desa";
|
||||
import StrukturOrganisasi from "./struktur-bumdes";
|
||||
|
||||
const Ekonomi = new Elysia({
|
||||
prefix: "/api/ekonomi",
|
||||
prefix: "/ekonomi",
|
||||
tags: ["Ekonomi"],
|
||||
})
|
||||
.use(PasarDesa)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fileStorageFindMany } from "./_lib/findMany";
|
||||
import fileStorageDelete from "./_lib/del";
|
||||
|
||||
const FileStorage = new Elysia({
|
||||
prefix: "/api/fileStorage",
|
||||
prefix: "/fileStorage",
|
||||
tags: ["FileStorage"],
|
||||
})
|
||||
.post("/create", fileStorageCreate, {
|
||||
|
||||
@@ -8,7 +8,7 @@ import LayananOnlineDesa from "./layanan-online-desa";
|
||||
import MitraKolaborasi from "./kolaborasi-inovasi/mitra-kolaborasi";
|
||||
|
||||
const Inovasi = new Elysia({
|
||||
prefix: "/api/inovasi",
|
||||
prefix: "/inovasi",
|
||||
tags: ["Inovasi"],
|
||||
})
|
||||
.use(DesaDigital)
|
||||
|
||||
@@ -9,7 +9,7 @@ import KontakDaruratKeamanan from "./kontak-darurat-keamanan";
|
||||
import KontakItem from "./kontak-darurat-keamanan/kontak-item";
|
||||
import LayananPolsek from "./polsek-terdekat/layanan-polsek";
|
||||
|
||||
const Keamanan = new Elysia({ prefix: "/api/keamanan", tags: ["Keamanan"] })
|
||||
const Keamanan = new Elysia({ prefix: "/keamanan", tags: ["Keamanan"] })
|
||||
.use(KeamananLingkungan)
|
||||
.use(PolsekTerdekat)
|
||||
.use(PencegahanKriminalitas)
|
||||
|
||||
@@ -24,7 +24,7 @@ import TarifLayanan from "./data_kesehatan_warga/fasilitas_kesehatan/tarif-layan
|
||||
|
||||
|
||||
const Kesehatan = new Elysia({
|
||||
prefix: "/api/kesehatan",
|
||||
prefix: "/kesehatan",
|
||||
tags: ["Kesehatan"],
|
||||
})
|
||||
.use(PersentaseKelahiranKematian)
|
||||
|
||||
@@ -14,7 +14,7 @@ import UmurResponden from "./indeks_kepuasan/umur-responden";
|
||||
import Responden from "./indeks_kepuasan/responden";
|
||||
|
||||
const LandingPage = new Elysia({
|
||||
prefix: "/api/landingpage",
|
||||
prefix: "/landingpage",
|
||||
tags: ["Landing Page/Profile"]
|
||||
})
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import KategoriKegiatan from "./gotong-royong/kategori-kegiatan";
|
||||
import KeteranganBankSampahTerdekat from "./pengelolaan-sampah/keterangan-bank-sampah";
|
||||
|
||||
const Lingkungan = new Elysia({
|
||||
prefix: "/api/lingkungan",
|
||||
prefix: "/lingkungan",
|
||||
tags: ["Lingkungan"],
|
||||
})
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import Beasiswa from "./beasiswa-desa";
|
||||
import PerpustakaanDigital from "./perpustakaan-digital";
|
||||
|
||||
const Pendidikan = new Elysia({
|
||||
prefix: "/api/pendidikan",
|
||||
prefix: "/pendidikan",
|
||||
tags: ["Pendidikan"]
|
||||
})
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import GrafikHasilKepuasanMasyarakat from "./ikm/grafik_hasil_kepuasan_masyaraka
|
||||
|
||||
|
||||
|
||||
const PPID = new Elysia({ prefix: "/api/ppid", tags: ["PPID"] })
|
||||
const PPID = new Elysia({ prefix: "/ppid", tags: ["PPID"] })
|
||||
.use(ProfilePPID)
|
||||
.use(DaftarInformasiPublik)
|
||||
.use(GrafikHasilKepuasanMasyarakat)
|
||||
|
||||
@@ -2,7 +2,7 @@ import Elysia from "elysia";
|
||||
import searchFindMany from "./findMany";
|
||||
|
||||
const Search = new Elysia({
|
||||
prefix: "/api/search",
|
||||
prefix: "/search",
|
||||
tags: ["Search"],
|
||||
})
|
||||
.get("/findMany", searchFindMany);
|
||||
|
||||
@@ -7,7 +7,7 @@ import userDelete from "./del"; // `delete` nggak boleh jadi nama file JS langsu
|
||||
import userUpdate from "./updt";
|
||||
import userDeleteAccount from "./delUser";
|
||||
|
||||
const User = new Elysia({ prefix: "/api/user" })
|
||||
const User = new Elysia({ prefix: "/user" })
|
||||
.get("/findMany", userFindMany)
|
||||
.get("/findUnique/:id", userFindUnique)
|
||||
.put("/del/:id", userDelete, {
|
||||
|
||||
@@ -6,7 +6,7 @@ import roleFindUnique from "./findUnique";
|
||||
import roleUpdate from "./updt";
|
||||
|
||||
const Role = new Elysia({
|
||||
prefix: "/api/role",
|
||||
prefix: "/role",
|
||||
tags: ["User / Role"],
|
||||
})
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ async function layanan() {
|
||||
}
|
||||
|
||||
const Utils = new Elysia({
|
||||
prefix: "/api/utils",
|
||||
prefix: "/utils",
|
||||
tags: ["Utils"],
|
||||
}).get("/version", async () => {
|
||||
const packageJson = await fs.readFile(
|
||||
@@ -81,8 +81,7 @@ const Utils = new Elysia({
|
||||
if (!process.env.WIBU_UPLOAD_DIR)
|
||||
throw new Error("WIBU_UPLOAD_DIR is not defined");
|
||||
|
||||
const ApiServer = new Elysia()
|
||||
.use(swagger({ path: "/api/docs" }))
|
||||
const ApiServer = new Elysia({ prefix: "/api" })
|
||||
.use(
|
||||
staticPlugin({
|
||||
assets: UPLOAD_DIR,
|
||||
@@ -90,6 +89,25 @@ const ApiServer = new Elysia()
|
||||
}),
|
||||
)
|
||||
.use(cors(corsConfig))
|
||||
.use(
|
||||
swagger({
|
||||
path: "/docs",
|
||||
documentation: {
|
||||
info: {
|
||||
title: "Desa Darmasaba API Documentation",
|
||||
version: "1.0.0",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
.onError(({ code }) => {
|
||||
if (code === "NOT_FOUND") {
|
||||
return {
|
||||
status: 404,
|
||||
body: "Route not found :(",
|
||||
};
|
||||
}
|
||||
})
|
||||
.use(Utils)
|
||||
.use(FileStorage)
|
||||
.use(LandingPage)
|
||||
@@ -104,126 +122,114 @@ const ApiServer = new Elysia()
|
||||
.use(User)
|
||||
.use(Role)
|
||||
.use(Search)
|
||||
|
||||
.onError(({ code }) => {
|
||||
if (code === "NOT_FOUND") {
|
||||
return {
|
||||
status: 404,
|
||||
body: "Route not found :(",
|
||||
};
|
||||
}
|
||||
})
|
||||
.group("/api", (app) =>
|
||||
app
|
||||
.get("/layanan", layanan)
|
||||
.get("/potensi", getPotensi)
|
||||
.get(
|
||||
"/img/:name",
|
||||
({ params, query }) => {
|
||||
return img({
|
||||
name: params.name,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
ROOT,
|
||||
size: query.size,
|
||||
});
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
query: t.Optional(
|
||||
t.Object({
|
||||
size: t.Optional(t.Number()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/img/:name",
|
||||
({ params }) => {
|
||||
return imgDel({
|
||||
name: params.name,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
});
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/imgs",
|
||||
({ query }) => {
|
||||
return imgs({
|
||||
search: query.search,
|
||||
page: query.page,
|
||||
count: query.count,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
});
|
||||
},
|
||||
{
|
||||
query: t.Optional(
|
||||
t.Object({
|
||||
page: t.Number({ default: 1 }),
|
||||
count: t.Number({ default: 10 }),
|
||||
search: t.String({ default: "" }),
|
||||
}),
|
||||
),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-img",
|
||||
({ body }) => {
|
||||
console.log(body.title);
|
||||
return uplImg({ files: body.files, UPLOAD_DIR_IMAGE });
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
files: t.Files({ multiple: true }),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-img-single",
|
||||
({ body }) => {
|
||||
return uplImgSingle({
|
||||
fileName: body.name,
|
||||
file: body.file,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
});
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
file: t.File(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-csv-single",
|
||||
({ body }) => {
|
||||
return uplCsvSingle({ fileName: body.name, file: body.file });
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
file: t.File(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-csv",
|
||||
({ body }) => {
|
||||
return uplCsv({ files: body.files });
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
files: t.Files(),
|
||||
}),
|
||||
},
|
||||
.get("/layanan", layanan)
|
||||
.get("/potensi", getPotensi)
|
||||
.get(
|
||||
"/img/:name",
|
||||
({ params, query }) => {
|
||||
return img({
|
||||
name: params.name,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
ROOT,
|
||||
size: query.size,
|
||||
});
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
query: t.Optional(
|
||||
t.Object({
|
||||
size: t.Optional(t.Number()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/img/:name",
|
||||
({ params }) => {
|
||||
return imgDel({
|
||||
name: params.name,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
});
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
name: t.String(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/imgs",
|
||||
({ query }) => {
|
||||
return imgs({
|
||||
search: query.search,
|
||||
page: query.page,
|
||||
count: query.count,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
});
|
||||
},
|
||||
{
|
||||
query: t.Optional(
|
||||
t.Object({
|
||||
page: t.Number({ default: 1 }),
|
||||
count: t.Number({ default: 10 }),
|
||||
search: t.String({ default: "" }),
|
||||
}),
|
||||
),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-img",
|
||||
({ body }) => {
|
||||
console.log(body.title);
|
||||
return uplImg({ files: body.files, UPLOAD_DIR_IMAGE });
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
title: t.String(),
|
||||
files: t.Files({ multiple: true }),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-img-single",
|
||||
({ body }) => {
|
||||
return uplImgSingle({
|
||||
fileName: body.name,
|
||||
file: body.file,
|
||||
UPLOAD_DIR_IMAGE,
|
||||
});
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
file: t.File(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-csv-single",
|
||||
({ body }) => {
|
||||
return uplCsvSingle({ fileName: body.name, file: body.file });
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
file: t.File(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/upl-csv",
|
||||
({ body }) => {
|
||||
return uplCsv({ files: body.files });
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
files: t.Files(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
export const GET = ApiServer.handle;
|
||||
|
||||
@@ -33,6 +33,8 @@ export async function POST(req: Request) {
|
||||
const codeOtp = randomOTP();
|
||||
const otpNumber = Number(codeOtp);
|
||||
|
||||
console.log(`🔑 DEBUG OTP [${nomor}]: ${codeOtp}`);
|
||||
|
||||
const waMessage = `Website Desa Darmasaba - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun Admin lainnya.\n\n>> Kode OTP anda: ${codeOtp}.`;
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`;
|
||||
|
||||
@@ -40,26 +42,19 @@ export async function POST(req: Request) {
|
||||
|
||||
try {
|
||||
const res = await fetch(waUrl);
|
||||
const sendWa = await res.json();
|
||||
console.log("📱 WA Response:", sendWa);
|
||||
|
||||
if (sendWa.status !== "success") {
|
||||
console.error("❌ WA Service Error:", sendWa);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Gagal mengirim OTP via WhatsApp",
|
||||
debug: sendWa
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
if (!res.ok) {
|
||||
console.error(`⚠️ WA Service HTTP Error: ${res.status} ${res.statusText}. Continuing since OTP is logged.`);
|
||||
console.log(`💡 Use this OTP to login: ${codeOtp}`);
|
||||
} else {
|
||||
const sendWa = await res.json();
|
||||
console.log("📱 WA Response:", sendWa);
|
||||
if (sendWa.status !== "success") {
|
||||
console.error("⚠️ WA Service Logic Error:", sendWa);
|
||||
}
|
||||
}
|
||||
} catch (waError) {
|
||||
console.error("❌ Fetch WA Error:", waError);
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Terjadi kesalahan saat mengirim WA" },
|
||||
{ status: 500 }
|
||||
);
|
||||
} catch (waError: unknown) {
|
||||
const errorMessage = waError instanceof Error ? waError.message : String(waError);
|
||||
console.error("⚠️ WA Connection Exception. Continuing since OTP is logged.", errorMessage);
|
||||
}
|
||||
|
||||
const createOtpId = await prisma.kodeOtp.create({
|
||||
|
||||
@@ -22,14 +22,22 @@ export async function POST(req: Request) {
|
||||
// ✅ Generate dan kirim OTP
|
||||
const codeOtp = randomOTP();
|
||||
const otpNumber = Number(codeOtp);
|
||||
console.log(`🔑 DEBUG REGISTER OTP [${nomor}]: ${codeOtp}`);
|
||||
|
||||
const waMessage = `Website Desa Darmasaba - Kode verifikasi Anda: ${codeOtp}`;
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`;
|
||||
const waRes = await fetch(waUrl);
|
||||
const waData = await waRes.json();
|
||||
|
||||
if (waData.status !== "success") {
|
||||
return NextResponse.json({ success: false, message: 'Gagal mengirim OTP via WhatsApp' }, { status: 400 });
|
||||
|
||||
try {
|
||||
const waRes = await fetch(waUrl);
|
||||
if (!waRes.ok) {
|
||||
console.warn(`⚠️ WA Service HTTP Error (Register): ${waRes.status} ${waRes.statusText}. Continuing since OTP is logged.`);
|
||||
} else {
|
||||
const waData = await waRes.json();
|
||||
console.log("📱 WA Response (Register):", waData);
|
||||
}
|
||||
} catch (waError: unknown) {
|
||||
const errorMessage = waError instanceof Error ? waError.message : String(waError);
|
||||
console.warn("⚠️ WA Connection Exception (Register). Continuing since OTP is logged.", errorMessage);
|
||||
}
|
||||
|
||||
// ✅ Simpan OTP ke database
|
||||
|
||||
@@ -17,18 +17,23 @@ export async function POST(req: Request) {
|
||||
|
||||
const codeOtp = randomOTP();
|
||||
const otpNumber = Number(codeOtp);
|
||||
console.log(`🔑 DEBUG RESEND OTP [${nomor}]: ${codeOtp}`);
|
||||
|
||||
// Kirim OTP via WhatsApp
|
||||
const waMessage = `Website Desa Darmasaba - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun Admin lainnya.\n\n>> Kode OTP anda: ${codeOtp}.`;
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=${encodeURIComponent(waMessage)}`;
|
||||
const waRes = await fetch(waUrl);
|
||||
const waData = await waRes.json();
|
||||
|
||||
if (waData.status !== "success") {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Gagal mengirim OTP via WhatsApp" },
|
||||
{ status: 400 }
|
||||
);
|
||||
|
||||
try {
|
||||
const waRes = await fetch(waUrl);
|
||||
if (!waRes.ok) {
|
||||
console.warn(`⚠️ WA Service HTTP Error (Resend): ${waRes.status} ${waRes.statusText}. Continuing since OTP is logged.`);
|
||||
} else {
|
||||
const waData = await waRes.json();
|
||||
console.log("📱 WA Response (Resend):", waData);
|
||||
}
|
||||
} catch (waError: unknown) {
|
||||
const errorMessage = waError instanceof Error ? waError.message : String(waError);
|
||||
console.warn("⚠️ WA Connection Exception (Resend). Continuing since OTP is logged.", errorMessage);
|
||||
}
|
||||
|
||||
// Simpan OTP ke database
|
||||
|
||||
@@ -21,14 +21,22 @@ export async function POST(req: Request) {
|
||||
// Generate OTP
|
||||
const codeOtp = randomOTP();
|
||||
const otpNumber = Number(codeOtp);
|
||||
console.log(`🔑 DEBUG SEND-OTP-REGISTER [${nomor}]: ${codeOtp}`);
|
||||
|
||||
// Kirim WA
|
||||
const waUrl = `https://wa.wibudev.com/code?nom=${encodeURIComponent(nomor)}&text=Website Desa Darmasaba - Kode verifikasi Anda: ${codeOtp}`;
|
||||
const res = await fetch(waUrl);
|
||||
const sendWa = await res.json();
|
||||
|
||||
if (sendWa.status !== "success") {
|
||||
return NextResponse.json({ success: false, message: 'Gagal mengirim OTP' }, { status: 400 });
|
||||
|
||||
try {
|
||||
const res = await fetch(waUrl);
|
||||
if (!res.ok) {
|
||||
console.warn(`⚠️ WA Service HTTP Error (SendOTPRegister): ${res.status} ${res.statusText}. Continuing since OTP is logged.`);
|
||||
} else {
|
||||
const sendWa = await res.json();
|
||||
console.log("📱 WA Response (SendOTPRegister):", sendWa);
|
||||
}
|
||||
} catch (waError: unknown) {
|
||||
const errorMessage = waError instanceof Error ? waError.message : String(waError);
|
||||
console.warn("⚠️ WA Connection Exception (SendOTPRegister). Continuing since OTP is logged.", errorMessage);
|
||||
}
|
||||
|
||||
// Simpan OTP
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Paper, Title, Progress, Stack, Text, Group, Box, rem } from '@mantine/core'
|
||||
import { Paper, Title, Progress, Stack, Text, Group, Box } from '@mantine/core'
|
||||
import { IconArrowUpRight, IconArrowDownRight } from '@tabler/icons-react'
|
||||
import { APBDes, APBDesItem, SummaryData } from '../types/apbdes'
|
||||
import { APBDes, APBDesItem } from '../types/apbdes'
|
||||
|
||||
interface SummaryProps {
|
||||
title: string
|
||||
@@ -107,7 +106,7 @@ function Summary({ title, data, icon }: SummaryProps) {
|
||||
|
||||
<Text
|
||||
fz="xs"
|
||||
c={statusMessage.color as any}
|
||||
c={statusMessage.color}
|
||||
fw={600}
|
||||
style={{
|
||||
backgroundColor: `var(--mantine-color-${statusMessage.color}-0)`,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Paper, Table, Title, Box, ScrollArea, Badge } from '@mantine/core'
|
||||
import { APBDes, APBDesItem } from '../types/apbdes'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Paper, Table, Title, Badge, Text, Box, ScrollArea } from '@mantine/core'
|
||||
import { APBDes, APBDesItem, RealisasiItem } from '../types/apbdes'
|
||||
|
||||
|
||||
@@ -29,16 +29,18 @@ process.on('unhandledRejection', async (error) => {
|
||||
});
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('Received SIGINT signal. Closing database connections...');
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
if (process.env.NODE_ENV === 'production' && !process.env.NEXT_PHASE) {
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('Received SIGINT signal. Closing database connections...');
|
||||
await prisma.$disconnect();
|
||||
// Allow natural exit
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Received SIGTERM signal. Closing database connections...');
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Received SIGTERM signal. Closing database connections...');
|
||||
await prisma.$disconnect();
|
||||
// Allow natural exit
|
||||
});
|
||||
}
|
||||
|
||||
export default prisma;
|
||||
Reference in New Issue
Block a user