Merge pull request 'amalia/20-mei-26' (#50) from amalia/20-mei-26 into join

Reviewed-on: #50
This commit is contained in:
2026-05-20 17:22:20 +08:00
5 changed files with 40 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "sistem-desa-mandiri", "name": "sistem-desa-mandiri",
"version": "0.1.14", "version": "0.1.15",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --experimental-https", "dev": "next dev --experimental-https",

View File

@@ -0,0 +1 @@
-- This is an empty migration.

View File

@@ -1,3 +1,4 @@
import { isValidApiKey } from "@/lib/apiKey";
import { prisma } from "@/module/_global"; import { prisma } from "@/module/_global";
import cors from "@elysiajs/cors"; import cors from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger"; import { swagger } from "@elysiajs/swagger";
@@ -6,20 +7,6 @@ import _ from "lodash";
import moment from "moment"; import moment from "moment";
import "moment/locale/id"; import "moment/locale/id";
const CACHE_TTL_MS = 60_000;
let apiKeyCache: Set<string> = new Set();
let cacheExpiresAt = 0;
async function isValidApiKey(incoming: string): Promise<boolean> {
const now = Date.now();
if (now > cacheExpiresAt) {
const rows = await prisma.apiKey.findMany({ where: { isActive: true }, select: { key: true } });
apiKeyCache = new Set(rows.map((r) => r.key));
cacheExpiresAt = now + CACHE_TTL_MS;
}
return apiKeyCache.has(incoming);
}
const AiServer = new Elysia({ prefix: "/api/ai" }) const AiServer = new Elysia({ prefix: "/api/ai" })
.use(cors({ .use(cors({
origin: "*", origin: "*",

View File

@@ -1,3 +1,4 @@
import { isValidApiKey } from "@/lib/apiKey";
import { prisma } from "@/module/_global"; import { prisma } from "@/module/_global";
import cors from "@elysiajs/cors"; import cors from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger"; import { swagger } from "@elysiajs/swagger";
@@ -11,20 +12,40 @@ const NocServer = new Elysia({ prefix: "/api/noc" })
.use(cors({ .use(cors({
origin: "*", origin: "*",
methods: ["GET", "POST", "OPTIONS"], methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: ["Content-Type", "x-api-key"],
})) }))
.use(swagger({ .use(swagger({
path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs path: "/docs",
documentation: { documentation: {
info: { info: {
title: "Sistem Desa Mandiri - NOC API", title: "Sistem Desa Mandiri - NOC API",
version: "1.0.0", version: "1.0.0",
description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa", description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa",
}, },
components: {
securitySchemes: {
ApiKeyAuth: {
type: "apiKey",
in: "header",
name: "x-api-key",
},
},
},
security: [{ ApiKeyAuth: [] }],
tags: [ tags: [
{ name: "NOC", description: "Endpoint khusus monitoring" } { name: "NOC", description: "Endpoint khusus monitoring" }
] ]
} }
})) }))
.onBeforeHandle(async ({ request, set, path }) => {
if (path.startsWith("/api/noc/docs")) return;
const incoming = request.headers.get("x-api-key");
if (!incoming || !(await isValidApiKey(incoming))) {
set.status = 401;
return { success: false, message: "Unauthorized" };
}
})
// ── GET /api/noc/active-divisions ────────────────────────────────────────── // ── GET /api/noc/active-divisions ──────────────────────────────────────────
.get( .get(

15
src/lib/apiKey.ts Normal file
View File

@@ -0,0 +1,15 @@
import { prisma } from "@/module/_global";
const CACHE_TTL_MS = 60_000;
let apiKeyCache: Set<string> = new Set();
let cacheExpiresAt = 0;
export async function isValidApiKey(incoming: string): Promise<boolean> {
const now = Date.now();
if (now > cacheExpiresAt) {
const rows = await prisma.apiKey.findMany({ where: { isActive: true }, select: { key: true } });
apiKeyCache = new Set(rows.map((r) => r.key));
cacheExpiresAt = now + CACHE_TTL_MS;
}
return apiKeyCache.has(incoming);
}