Compare commits

...

20 Commits

Author SHA1 Message Date
d1f553ee32 upd: api noc 2026-03-25 17:02:26 +08:00
b14ae8e5ff Merge pull request 'upd: api version' (#26) from amalia/16-mar-26 into join
Reviewed-on: #26
2026-03-16 16:13:58 +08:00
270875a95c upd: api version 2026-03-16 10:39:23 +08:00
09bd75d5e5 Merge pull request 'upd: api noc' (#25) from amalia/12-mar-26 into join
Reviewed-on: #25
2026-03-12 16:11:03 +08:00
339b1e25cc upd: api noc 2026-03-12 16:08:42 +08:00
d9c6f486a9 Merge pull request 'upd: api noc' (#24) from amalia/11-mar-26 into join
Reviewed-on: #24
2026-03-11 16:41:40 +08:00
1a20697f4c upd: api noc 2026-03-11 16:40:42 +08:00
3927a6b756 Merge pull request 'amalia/09-mar-26' (#23) from amalia/09-mar-26 into join
Reviewed-on: #23
2026-03-10 16:45:28 +08:00
079395654d update version dan data seeder 2026-03-09 11:34:13 +08:00
93e7f33f7c update version 2026-03-09 10:36:31 +08:00
aba7a4c8fc update workflow 2026-03-09 10:35:37 +08:00
f55b171987 Merge pull request 'amalia/06-mar-26' (#22) from amalia/06-mar-26 into join
Reviewed-on: #22
2026-03-06 16:37:01 +08:00
bipproduction
d401ebb208 fix: add custom Pages Router _error page for 404/500 prerendering
Override default Next.js _error page that imports <Html> from
next/document, which fails during Docker build prerendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:35:48 +08:00
bipproduction
5230a31942 fix: add custom 404 and global error pages for Docker build
Create not-found.tsx and global-error.tsx to prevent Next.js from
falling back to legacy Pages Router _error pages during static
generation, which fail with useContext null in Docker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:28:37 +08:00
bipproduction
5e7eb20c26 fix: force dynamic rendering to skip static prerendering
Add `export const dynamic = 'force-dynamic'` to root layout so all
pages are rendered at request time instead of build time. Fixes
useContext null error during Docker build prerendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:19:36 +08:00
bipproduction
b7063d3658 fix: update Dockerfile bun version and remove --ignore-scripts
Upgrade bun 1.3.1 to 1.3.6 and remove --ignore-scripts flag to fix
prerender error during Docker build (null useContext dispatcher).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:12:24 +08:00
4abaa97cc0 upd bun 2026-03-06 10:59:14 +08:00
069174cba1 update env example dummy 2026-03-06 10:42:45 +08:00
a04e0186a2 update env example 2026-03-06 10:33:22 +08:00
2af22b4bc7 build 2026-03-06 10:21:40 +08:00
14 changed files with 2014 additions and 7 deletions

52
.env.example Normal file
View File

@@ -0,0 +1,52 @@
# ===========================================
# SISTEM DESA MANDIRI - ENVIRONMENT VARIABLES
# ===========================================
# Copy this file to .env and fill in the appropriate values
# ===========================================
# DATABASE CONFIGURATION
# ===========================================
# PostgreSQL, MySQL, or SQLite connection string
# Example (PostgreSQL): postgresql://user:password@localhost:5432/dbname
# Example (MySQL): mysql://user:password@localhost:3306/dbname
# Example (SQLite): file:./dev.db
DATABASE_URL="your-database-url-here"
# ===========================================
# FIREBASE ADMIN SDK (For FCM Push Notifications)
# ===========================================
# Google Cloud project ID
GOOGLE_PROJECT_ID="your-google-project-id"
# Google service account client email
GOOGLE_CLIENT_EMAIL="your-service-account-email@your-project.iam.gserviceaccount.com"
# Google service account private key (include the full key with newlines)
GOOGLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----"
# Google service account private key ID (optional but recommended)
GOOGLE_PRIVATE_KEY_ID="your-private-key-id"
# ===========================================
# WEB PUSH NOTIFICATIONS (VAPID Keys)
# ===========================================
# VAPID public key (exposed to client-side, must start with NEXT_PUBLIC_)
NEXT_PUBLIC_VAPID_PUBLIC_KEY="BJlglqrIZCbPCZyUs8UIzEP1Wi18hzvGaC3-KPLkQuoCV_EOKdyGJNbu7fs5jYaO571ipVAMko8YiwIMa1VjQEg"
# VAPID private key (keep secret, server-side only)
VAPID_PRIVATE_KEY="UHDY8M3-0beVIA2kt2zL3ZeMStJ0j6zVkVd2Cfqpgrc"
# ===========================================
# FILE STORAGE / WEBSOCKET API
# ===========================================
# API key for file operations (upload, delete, copy, view directory)
WS_APIKEY="your-websocket-api-key"
# ===========================================
# APPLICATION SETTINGS
# ===========================================
# Next.js node environment (development, production, test)
NODE_ENV="development"
# Application URL (optional, for reference)
NEXT_PUBLIC_APP_URL="http://localhost:3000"

76
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
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
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: 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

37
.github/workflows/re-pull.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
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 repository
uses: actions/checkout@v4
- 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 }}

93
.github/workflows/script/re-pull.sh vendored Normal file
View File

@@ -0,0 +1,93 @@
#!/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}"
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!"
echo " Pastikan nama stack sudah benar."
exit 1
fi
STACK_ID=$(echo "$STACK" | jq -r .Id)
ENDPOINT_ID=$(echo "$STACK" | jq -r .EndpointId)
ENV=$(echo "$STACK" | jq '.Env // []')
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 "🚀 Redeploying $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 container running..."
MAX_RETRY=15
COUNT=0
while [ $COUNT -lt $MAX_RETRY ]; do
sleep 5
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}")
TOTAL=$(echo "$CONTAINERS" | jq 'length')
RUNNING=$(echo "$CONTAINERS" | jq '[.[] | select(.State == "running")] | length')
FAILED=$(echo "$CONTAINERS" | jq '[.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not))] | length')
echo "🔄 [${COUNT}/${MAX_RETRY}] Running: ${RUNNING} | Failed: ${FAILED} | Total: ${TOTAL}"
echo "$CONTAINERS" | jq -r '.[] | " → \(.Names[0]) | \(.State) | \(.Status)"'
if [ "$FAILED" -gt "0" ]; then
echo ""
echo "❌ Ada container yang crash!"
echo "$CONTAINERS" | jq -r '.[] | select(.State == "exited" and (.Status | test("Exited \\(0\\)") | not)) | " → \(.Names[0]) | \(.Status)"'
exit 1
fi
if [ "$RUNNING" -gt "0" ]; then
echo ""
echo "✅ Stack $STACK_NAME berhasil di-redeploy dan running!"
exit 0
fi
done
echo ""
echo "❌ Timeout! Stack tidak kunjung running setelah $((MAX_RETRY * 5)) detik."
exit 1

83
Dockerfile Normal file
View File

@@ -0,0 +1,83 @@
# ==============================
# Stage 1: Builder (Bun)
# ==============================
FROM oven/bun:1.3.6-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* ./
COPY prisma ./prisma
ENV ONNXRUNTIME_NODE_INSTALL_CUDA=0
ENV SHARP_IGNORE_GLOBAL_LIBVIPS=1
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun install
COPY . .
# Gunakan .env jika ada, fallback ke .env.example.
# Untuk build dengan .env custom, hapus .env dari .dockerignore
# atau berikan via: docker build --secret id=env,src=.env (BuildKit)
RUN if [ -f .env ]; then \
echo "INFO: Menggunakan .env"; \
elif [ -f .env.example ]; then \
cp .env.example .env; \
echo "WARNING: .env tidak ditemukan, menggunakan .env.example (isi dengan nilai yang benar)"; \
else \
echo "WARNING: Tidak ada .env atau .env.example"; \
fi
# Generate prisma client
RUN ./node_modules/.bin/prisma generate
# Build Next.js
RUN bun run build
# ==============================
# Stage 2: Runner (Bun)
# ==============================
FROM oven/bun:1.3.6-debian AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
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 /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/tsconfig.json ./tsconfig.json
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/src ./src
# Env vars runtime dikelola oleh Portainer (stack env / container env).
# Tidak perlu copy .env ke runner — image tetap bersih tanpa secrets.
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["bun", "run", "start"]

View File

@@ -0,0 +1,879 @@
-- CreateTable
CREATE TABLE "AdminRole" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AdminRole_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Admin" (
"id" TEXT NOT NULL,
"idAdminRole" TEXT NOT NULL,
"name" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"email" TEXT,
"gender" TEXT NOT NULL DEFAULT 'M',
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Admin_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserRole" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "UserRole_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Village" (
"id" TEXT NOT NULL,
"idTheme" TEXT,
"name" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Village_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Group" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Group_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Position" (
"id" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"name" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Position_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"idUserRole" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"idPosition" TEXT,
"nik" TEXT NOT NULL,
"name" TEXT NOT NULL,
"phone" TEXT NOT NULL,
"email" TEXT,
"gender" TEXT NOT NULL DEFAULT 'M',
"img" TEXT,
"isFirstLogin" BOOLEAN NOT NULL DEFAULT true,
"isWithoutOTP" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TokenDeviceUser" (
"id" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"token" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TokenDeviceUser_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UserLog" (
"id" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"action" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"idContent" TEXT NOT NULL,
"tbContent" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "UserLog_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Announcement" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Announcement_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AnnouncementMember" (
"id" TEXT NOT NULL,
"idAnnouncement" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AnnouncementMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AnnouncementFile" (
"id" TEXT NOT NULL,
"idAnnouncement" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AnnouncementFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Project" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 0,
"desc" TEXT,
"reason" TEXT,
"report" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectMember" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isLeader" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectFile" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectLink" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"link" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectLink_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectTask" (
"id" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"notifikasi" BOOLEAN NOT NULL DEFAULT false,
"dateStart" DATE NOT NULL,
"dateEnd" DATE NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectTask_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectTaskDetail" (
"id" TEXT NOT NULL,
"idTask" TEXT NOT NULL,
"date" DATE NOT NULL,
"timeStart" TIME,
"timeEnd" TIME,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProjectTaskDetail_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Division" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"name" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Division_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionMember" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"isLeader" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProject" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT,
"reason" TEXT,
"report" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProject_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectLink" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"link" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectLink_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectTask" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"notifikasi" BOOLEAN NOT NULL DEFAULT false,
"dateStart" DATE NOT NULL,
"dateEnd" DATE NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectTask_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectTaskDetail" (
"id" TEXT NOT NULL,
"idTask" TEXT NOT NULL,
"date" DATE NOT NULL,
"timeStart" TIME,
"timeEnd" TIME,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectTaskDetail_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectMember" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isLeader" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionProjectFile" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idProject" TEXT NOT NULL,
"idFile" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionProjectFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDisscussion" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"title" TEXT,
"desc" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 1,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDisscussion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDisscussionComment" (
"id" TEXT NOT NULL,
"idDisscussion" TEXT NOT NULL,
"comment" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"isEdited" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDisscussionComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDiscussionFile" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDiscussionFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDocumentFolderFile" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idStorage" TEXT,
"category" TEXT NOT NULL DEFAULT 'FOLDER',
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"path" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDocumentFolderFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionDocumentShare" (
"id" TEXT NOT NULL,
"idDocument" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionDocumentShare_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionCalendar" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"linkMeet" TEXT,
"dateStart" DATE NOT NULL,
"dateEnd" DATE,
"timeStart" TIME NOT NULL,
"timeEnd" TIME NOT NULL,
"repeatEventTyper" TEXT NOT NULL,
"repeatValue" INTEGER NOT NULL DEFAULT 1,
"reminderInterval" TEXT,
"status" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "DivisionCalendar_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionCalendarReminder" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idCalendar" TEXT NOT NULL,
"dateStart" DATE NOT NULL,
"dateEnd" DATE,
"timeStart" TIME NOT NULL,
"timeEnd" TIME NOT NULL,
"status" INTEGER NOT NULL DEFAULT 0,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionCalendarReminder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DivisionCalendarMember" (
"id" TEXT NOT NULL,
"idCalendar" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DivisionCalendarMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContainerImage" (
"id" TEXT NOT NULL,
"category" TEXT NOT NULL,
"idCategory" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ContainerImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContainerFileDivision" (
"id" TEXT NOT NULL,
"idDivision" TEXT NOT NULL,
"idStorage" TEXT,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ContainerFileDivision_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ColorTheme" (
"id" TEXT NOT NULL,
"idVillage" TEXT,
"name" TEXT NOT NULL,
"utama" TEXT NOT NULL,
"bgUtama" TEXT NOT NULL,
"bgIcon" TEXT NOT NULL,
"bgFiturHome" TEXT NOT NULL,
"bgFiturDivision" TEXT NOT NULL,
"bgTotalKegiatan" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ColorTheme_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "BannerImage" (
"id" TEXT NOT NULL,
"idVillage" TEXT,
"title" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"image" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BannerImage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Notifications" (
"id" TEXT NOT NULL,
"idUserTo" TEXT NOT NULL,
"idUserFrom" TEXT NOT NULL,
"category" TEXT NOT NULL,
"idContent" TEXT NOT NULL,
"title" TEXT NOT NULL,
"desc" TEXT NOT NULL,
"isRead" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Notifications_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Subscribe" (
"id" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"subscription" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3),
CONSTRAINT "Subscribe_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Discussion" (
"id" TEXT NOT NULL,
"idVillage" TEXT NOT NULL,
"idGroup" TEXT NOT NULL,
"title" TEXT,
"desc" TEXT NOT NULL,
"status" INTEGER NOT NULL DEFAULT 1,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Discussion_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscussionMember" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscussionMember_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscussionComment" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"idUser" TEXT NOT NULL,
"comment" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"isEdited" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscussionComment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscussionFile" (
"id" TEXT NOT NULL,
"idDiscussion" TEXT NOT NULL,
"name" TEXT NOT NULL,
"extension" TEXT NOT NULL,
"idStorage" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscussionFile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Setting" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Setting_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Admin_phone_key" ON "Admin"("phone");
-- CreateIndex
CREATE UNIQUE INDEX "Admin_email_key" ON "Admin"("email");
-- CreateIndex
CREATE UNIQUE INDEX "User_nik_key" ON "User"("nik");
-- CreateIndex
CREATE UNIQUE INDEX "User_phone_key" ON "User"("phone");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Subscribe_idUser_key" ON "Subscribe"("idUser");
-- AddForeignKey
ALTER TABLE "Admin" ADD CONSTRAINT "Admin_idAdminRole_fkey" FOREIGN KEY ("idAdminRole") REFERENCES "AdminRole"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Group" ADD CONSTRAINT "Group_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Position" ADD CONSTRAINT "Position_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idUserRole_fkey" FOREIGN KEY ("idUserRole") REFERENCES "UserRole"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_idPosition_fkey" FOREIGN KEY ("idPosition") REFERENCES "Position"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TokenDeviceUser" ADD CONSTRAINT "TokenDeviceUser_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UserLog" ADD CONSTRAINT "UserLog_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Announcement" ADD CONSTRAINT "Announcement_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Announcement" ADD CONSTRAINT "Announcement_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementMember" ADD CONSTRAINT "AnnouncementMember_idAnnouncement_fkey" FOREIGN KEY ("idAnnouncement") REFERENCES "Announcement"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementMember" ADD CONSTRAINT "AnnouncementMember_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementMember" ADD CONSTRAINT "AnnouncementMember_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AnnouncementFile" ADD CONSTRAINT "AnnouncementFile_idAnnouncement_fkey" FOREIGN KEY ("idAnnouncement") REFERENCES "Announcement"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectLink" ADD CONSTRAINT "ProjectLink_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectTask" ADD CONSTRAINT "ProjectTask_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectTaskDetail" ADD CONSTRAINT "ProjectTaskDetail_idTask_fkey" FOREIGN KEY ("idTask") REFERENCES "ProjectTask"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Division" ADD CONSTRAINT "Division_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Division" ADD CONSTRAINT "Division_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Division" ADD CONSTRAINT "Division_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionMember" ADD CONSTRAINT "DivisionMember_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionMember" ADD CONSTRAINT "DivisionMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProject" ADD CONSTRAINT "DivisionProject_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectLink" ADD CONSTRAINT "DivisionProjectLink_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectLink" ADD CONSTRAINT "DivisionProjectLink_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectTask" ADD CONSTRAINT "DivisionProjectTask_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectTask" ADD CONSTRAINT "DivisionProjectTask_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectTaskDetail" ADD CONSTRAINT "DivisionProjectTaskDetail_idTask_fkey" FOREIGN KEY ("idTask") REFERENCES "DivisionProjectTask"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectMember" ADD CONSTRAINT "DivisionProjectMember_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectMember" ADD CONSTRAINT "DivisionProjectMember_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectMember" ADD CONSTRAINT "DivisionProjectMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_idProject_fkey" FOREIGN KEY ("idProject") REFERENCES "DivisionProject"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_idFile_fkey" FOREIGN KEY ("idFile") REFERENCES "ContainerFileDivision"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionProjectFile" ADD CONSTRAINT "DivisionProjectFile_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussion" ADD CONSTRAINT "DivisionDisscussion_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussion" ADD CONSTRAINT "DivisionDisscussion_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussionComment" ADD CONSTRAINT "DivisionDisscussionComment_idDisscussion_fkey" FOREIGN KEY ("idDisscussion") REFERENCES "DivisionDisscussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDisscussionComment" ADD CONSTRAINT "DivisionDisscussionComment_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDiscussionFile" ADD CONSTRAINT "DivisionDiscussionFile_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "DivisionDisscussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentFolderFile" ADD CONSTRAINT "DivisionDocumentFolderFile_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentFolderFile" ADD CONSTRAINT "DivisionDocumentFolderFile_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentShare" ADD CONSTRAINT "DivisionDocumentShare_idDocument_fkey" FOREIGN KEY ("idDocument") REFERENCES "DivisionDocumentFolderFile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionDocumentShare" ADD CONSTRAINT "DivisionDocumentShare_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendar" ADD CONSTRAINT "DivisionCalendar_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendar" ADD CONSTRAINT "DivisionCalendar_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarReminder" ADD CONSTRAINT "DivisionCalendarReminder_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarReminder" ADD CONSTRAINT "DivisionCalendarReminder_idCalendar_fkey" FOREIGN KEY ("idCalendar") REFERENCES "DivisionCalendar"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarMember" ADD CONSTRAINT "DivisionCalendarMember_idCalendar_fkey" FOREIGN KEY ("idCalendar") REFERENCES "DivisionCalendar"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DivisionCalendarMember" ADD CONSTRAINT "DivisionCalendarMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ContainerFileDivision" ADD CONSTRAINT "ContainerFileDivision_idDivision_fkey" FOREIGN KEY ("idDivision") REFERENCES "Division"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ColorTheme" ADD CONSTRAINT "ColorTheme_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "BannerImage" ADD CONSTRAINT "BannerImage_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notifications" ADD CONSTRAINT "UserToUserMap" FOREIGN KEY ("idUserTo") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notifications" ADD CONSTRAINT "UserFromUserMap" FOREIGN KEY ("idUserFrom") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Subscribe" ADD CONSTRAINT "Subscribe_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Discussion" ADD CONSTRAINT "Discussion_idVillage_fkey" FOREIGN KEY ("idVillage") REFERENCES "Village"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Discussion" ADD CONSTRAINT "Discussion_idGroup_fkey" FOREIGN KEY ("idGroup") REFERENCES "Group"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Discussion" ADD CONSTRAINT "Discussion_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionMember" ADD CONSTRAINT "DiscussionMember_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "Discussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionMember" ADD CONSTRAINT "DiscussionMember_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "Discussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_idUser_fkey" FOREIGN KEY ("idUser") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscussionFile" ADD CONSTRAINT "DiscussionFile_idDiscussion_fkey" FOREIGN KEY ("idDiscussion") REFERENCES "Discussion"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@@ -0,0 +1,687 @@
import { prisma } from "@/module/_global";
import cors from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
import Elysia, { t } from "elysia";
import _ from "lodash";
import moment from "moment";
import "moment/locale/id";
// Gabungkan semua ke dalam satu instance server yang dipasang di /api/noc
const NocServer = new Elysia({ prefix: "/api/noc" })
.use(cors({
origin: "*",
methods: ["GET", "POST", "OPTIONS"],
}))
.use(swagger({
path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs
documentation: {
info: {
title: "Sistem Desa Mandiri - NOC API",
version: "1.0.0",
description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa",
},
tags: [
{ name: "NOC", description: "Endpoint khusus monitoring" }
]
}
}))
// ── GET /api/noc/active-divisions ──────────────────────────────────────────
.get(
"/active-divisions",
async ({ query, set }) => {
const { idDesa, limit } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
const maxResults = Number(limit ?? 5);
try {
// Cek apakah desa ada
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
// Ambil semua divisi milik desa ini
const divisions = await prisma.division.findMany({
where: {
idVillage: idDesa,
isActive: true,
},
select: {
id: true,
name: true,
idGroup: true,
Group: {
select: {
name: true,
},
},
_count: {
select: {
DivisionProject: true,
},
},
},
});
// Hitung total kegiatan per divisi & urutkan descending, ambil top sesuai limit
const ranked = divisions
.map((d: any) => ({
id: d.id,
division: d.name,
group: d.Group.name,
totalKegiatan: d._count.DivisionProject
}))
.sort((a: any, b: any) => b.totalKegiatan - a.totalKegiatan)
.slice(0, maxResults);
return {
success: true,
message: "Berhasil mendapatkan divisi teraktif",
data: {
idDesa: village.id,
namaDesa: village.name,
divisi: ranked,
},
};
} catch (error) {
console.error("[NOC] active-divisions error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(t.String({ description: "Jumlah maksimal data (default: 5)" })),
}),
detail: {
summary: "Divisi Teraktif",
description: "Menu Beranda - Mendapatkan daftar divisi teraktif berdasarkan jumlah proyek pada desa tertentu.",
tags: ["NOC"],
},
}
)
// ── GET /api/noc/latest-projects ──────────────────────────────────────────
.get(
"/latest-projects",
async ({ query, set }) => {
const { idDesa, limit } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
const maxResults = Math.min(Number(limit ?? 5), 50);
try {
// Cek apakah desa ada
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
// Ambil proyek umum terbaru dari desa ini
const projects = await prisma.project.findMany({
where: {
idVillage: idDesa,
isActive: true,
},
select: {
id: true,
title: true,
status: true,
desc: true,
updatedAt: true,
Group: {
select: {
name: true,
},
},
User: {
select: {
name: true,
},
},
},
orderBy: {
updatedAt: "desc",
},
take: maxResults,
});
const mapped = projects.map((p: any) => ({
id: p.id,
title: p.title,
status: p.status,
desc: p.desc,
group: p.Group.name,
createdBy: p.User.name,
updatedAt: p.updatedAt,
}));
return {
success: true,
message: "Berhasil mendapatkan proyek terbaru",
data: {
idDesa: village.id,
namaDesa: village.name,
total: mapped.length,
projects: mapped,
},
};
} catch (error) {
console.error("[NOC] latest-projects error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(
t.String({ description: "Jumlah maksimal proyek (default: 5, maks: 50)" })
),
}),
detail: {
summary: "Latest Projects General",
description: "Menu kinerja divisi - Mendapatkan daftar proyek umum terbaru dari berbagai grup pada desa tertentu.",
tags: ["NOC"],
},
}
)
// ── GET /api/noc/upcoming-events ───────────────────────────────────────────
.get(
"/upcoming-events",
async ({ query, set }) => {
const { idDesa, limit, filter } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
const maxResults = Math.min(Number(limit ?? 10), 50);
const today = moment().startOf("day").toDate();
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const events = await prisma.divisionCalendarReminder.findMany({
where: {
isActive: true,
dateStart: {
gte: today,
},
Division: {
idVillage: idDesa,
isActive: true,
},
DivisionCalendar: {
isActive: true,
},
},
select: {
id: true,
idCalendar: true,
dateStart: true,
dateEnd: true,
timeStart: true,
timeEnd: true,
status: true,
Division: {
select: {
id: true,
name: true,
},
},
DivisionCalendar: {
select: {
title: true,
desc: true,
linkMeet: true,
repeatEventTyper: true,
User: {
select: {
name: true,
},
},
},
},
},
orderBy: [
{ dateStart: "asc" },
{ timeStart: "asc" },
],
take: maxResults,
});
const todayMoment = moment().startOf("day");
const mapper = (e: any) => ({
id: e.id,
idCalendar: e.idCalendar,
title: e.DivisionCalendar.title,
desc: e.DivisionCalendar.desc,
linkMeet: e.DivisionCalendar.linkMeet ?? null,
repeatEventTyper: e.DivisionCalendar.repeatEventTyper,
dateStart: moment(e.dateStart).format("YYYY-MM-DD"),
dateEnd: e.dateEnd
? moment(e.dateEnd).format("YYYY-MM-DD")
: null,
timeStart: moment.utc(e.timeStart).format("HH:mm"),
timeEnd: moment.utc(e.timeEnd).format("HH:mm"),
status: e.status,
createdBy: e.DivisionCalendar.User.name,
divisi: {
id: e.Division.id,
name: e.Division.name,
},
});
const todayEvents = events.filter((e: any) => moment(e.dateStart).isSame(todayMoment, 'day')).map(mapper);
const upcomingEvents = events.filter((e: any) => moment(e.dateStart).isAfter(todayMoment, 'day')).map(mapper);
let data: any = {
idDesa: village.id,
namaDesa: village.name,
};
if (filter === "today") {
data.events = todayEvents;
} else if (filter === "upcoming") {
data.events = upcomingEvents;
} else {
data.today = todayEvents;
data.upcoming = upcomingEvents;
}
return {
success: true,
message: "Berhasil mendapatkan events",
data: data,
};
} catch (error) {
console.error("[NOC] upcoming-events error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(
t.String({ description: "Jumlah maksimal event (default: 10, maks: 50)" })
),
filter: t.Optional(
t.String({ description: "Filter event: 'today' atau 'upcoming'" })
),
}),
detail: {
summary: "Events (Today & Upcoming)",
description: "Menu beranda dan kinerja divisi - Mendapatkan daftar event pada hari ini dan yang akan datang untuk semua divisi pada desa tertentu.",
tags: ["NOC"],
},
}
)
// ── GET /api/noc/diagram-jumlah-document ───────────────────────────────────────────────
.get(
"/diagram-jumlah-document",
async ({ query, set }) => {
const { idDesa } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const documents = await prisma.divisionDocumentFolderFile.findMany({
where: {
isActive: true,
category: 'FILE',
Division: {
isActive: true,
idVillage: idDesa,
Group: {
isActive: true,
}
}
}
})
const groupData = _.map(_.groupBy(documents, "extension"), (v: any) => ({
file: v[0].extension,
jumlah: v.length,
}))
const image = ['jpg', 'jpeg', 'png', 'heic']
let hasilImage = {
label: 'Gambar',
value: 0,
color: '#fac858'
}
let hasilFile = {
label: 'Dokumen',
value: 0,
color: '#92cc76'
}
groupData.map((v: any) => {
if (image.some((i: any) => i == v.file)) {
hasilImage = {
label: 'Gambar',
value: hasilImage.value + v.jumlah,
color: '#fac858'
}
} else {
hasilFile = {
label: 'Dokumen',
value: hasilFile.value + v.jumlah,
color: '#92cc76'
}
}
})
const allData = [hasilImage, hasilFile]
return {
success: true,
message: "Berhasil mendapatkan jumlah document",
data: allData
};
} catch (error) {
console.error("[NOC] jumlah-document error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
}),
detail: {
summary: "Diagram Jumlah Document",
description: "Menu kinerja divisi - Mendapatkan diagram jumlah document pada desa tertentu.",
tags: ["NOC"],
},
}
)
// -- GET /api/noc/diagram-progres-kegiatan
.get(
"/diagram-progres-kegiatan",
async ({ query, set }) => {
const { idDesa } = query;
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const data = await prisma.project.groupBy({
where: {
isActive: true,
idVillage: idDesa,
Group: {
isActive: true,
}
},
by: ["status"],
_count: true
})
const dataStatus = [{ name: 'Segera dikerjakan', status: 0, color: '#177AD5' }, { name: 'Dikerjakan', status: 1, color: '#fac858' }, { name: 'Selesai dikerjakan', status: 2, color: '#92cc76' }, { name: 'Dibatalkan', status: 3, color: '#ED6665' }]
const hasil: any[] = []
let input
for (let index = 0; index < dataStatus.length; index++) {
const cek = data.some((i: any) => i.status == dataStatus[index].status)
if (cek) {
const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n: any, { _count }: any) => n + _count, 0)).toFixed(2)
const fix = find != "100.00" ? find.substr(-2, 2) == "00" ? find.substr(0, 2) : find : "100"
input = {
text: fix + '%',
value: fix,
color: dataStatus[index].color
}
} else {
input = {
text: '0%',
value: 0,
color: dataStatus[index].color
}
}
hasil.push(input)
}
return {
success: true,
message: "Berhasil mendapatkan progres kegiatan",
data: hasil
};
} catch (error) {
console.error("[NOC] progres-kegiatan error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
}),
detail: {
summary: "Diagram Progres Kegiatan",
description: "Menu kinerja divisi - Mendapatkan diagram progres kegiatan pada desa tertentu.",
tags: ["NOC"],
},
}
)
// -- GET /api/noc/latest-discussion
.get(
"/latest-discussion",
async ({ query, set }) => {
const { idDesa, limit } = query;
const maxResults = Math.min(Number(limit ?? 5), 50);
if (!idDesa) {
set.status = 400;
return {
success: false,
message: "Parameter idDesa wajib diisi",
data: null,
};
}
try {
const village = await prisma.village.findUnique({
where: { id: idDesa },
select: { id: true, name: true },
});
if (!village) {
set.status = 404;
return {
success: false,
message: "Desa tidak ditemukan",
data: null,
};
}
const data = await prisma.discussion.findMany({
take: maxResults,
where: {
idVillage: idDesa,
isActive: true,
status: 1,
},
select: {
id: true,
title: true,
desc: true,
createdAt: true,
User: {
select: {
name: true
}
},
Group: {
select: {
name: true
}
}
},
orderBy: {
createdAt: "desc"
}
})
const allData = data.map((v: any) => ({
..._.omit(v, ["createdAt", "User", "Group"]),
date: moment(v.createdAt).format("ll"),
user: v.User.name,
group: v.Group.name
}))
return {
success: true,
message: "Berhasil mendapatkan latest discussion",
data: allData
};
} catch (error) {
console.error("[NOC] latest-discussion error:", error);
set.status = 500;
return {
success: false,
message: "Terjadi kesalahan pada server",
data: null,
};
}
},
{
query: t.Object({
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
limit: t.Optional(t.String({ description: "Limit data" })),
}),
detail: {
summary: "Latest Discussion",
description: "Menu kinerja divisi - Mendapatkan latest discussion pada desa tertentu.",
tags: ["NOC"],
},
}
);
export const GET = NocServer.handle;
export const POST = NocServer.handle;

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
export async function GET(request: Request) {
try {
return NextResponse.json({ success: true, version: "2.1.3", tahap: "beta", update: "-revisi api mobile pengumuman, diskusi umum dan diskusi divisi; -ditambah kan file " }, { status: 200 });
return NextResponse.json({ success: true, version: "2.1.7", tahap: "beta", update: "-api untuk dashboard noc" }, { status: 200 });
} catch (error) {
console.error(error);
return NextResponse.json({ success: false, version: "Gagal mendapatkan version, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });

41
src/app/global-error.tsx Normal file
View File

@@ -0,0 +1,41 @@
"use client";
export default function GlobalError({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="en">
<body
style={{
backgroundColor: "#252A2F",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
gap: 16,
fontFamily: "Lato, sans-serif",
color: "white",
}}
>
<h2>Terjadi Kesalahan</h2>
<button
onClick={() => reset()}
style={{
padding: "8px 16px",
borderRadius: 4,
border: "1px solid #ccc",
background: "transparent",
color: "white",
cursor: "pointer",
}}
>
Coba Lagi
</button>
</body>
</html>
);
}

View File

@@ -9,6 +9,8 @@ import '@mantine/notifications/styles.css';
import { Lato } from "next/font/google";
import { Toaster } from 'react-hot-toast';
export const dynamic = 'force-dynamic';
export const metadata = {
title: "SISTEM DESA MANDIRI",
description: "I have followed setup instructions carefully",

24
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,24 @@
import { Box, Text, Button } from "@mantine/core";
import Link from "next/link";
export default function NotFound() {
return (
<Box
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
gap: 16,
}}
>
<Text size="xl" fw={700} c="white">
404 - Halaman Tidak Ditemukan
</Text>
<Button component={Link} href="/" variant="light">
Kembali ke Beranda
</Button>
</Box>
);
}

View File

@@ -19,7 +19,7 @@
"idPosition": "pos_ketua_rt01",
"nik": "3201010101010001",
"name": "Juli Ningrum",
"phone": "081234567890",
"phone": "6281234567890",
"email": "juliningrum@gmail.com",
"gender": "F"
},
@@ -31,7 +31,7 @@
"idPosition": "pos_sekretaris_rt01",
"nik": "3201010101010002",
"name": "Salwa Kusmawati",
"phone": "081234567891",
"phone": "6281234567891",
"email": "salwakusmawati@gmail.com",
"gender": "F"
},
@@ -43,7 +43,7 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010005",
"name": "Bakidin Wibowo",
"phone": "081234567894",
"phone": "6281234567894",
"email": "bakidinwibowo@gmail.com",
"gender": "M"
},
@@ -55,7 +55,7 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010006",
"name": "Jais Kurniawan",
"phone": "081234567895",
"phone": "6281234567895",
"email": "jaiskurniawan@gmail.com",
"gender": "M"
},
@@ -67,7 +67,7 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010007",
"name": "Safira Oktaviani S.I.Kom",
"phone": "081234567896",
"phone": "6281234567896",
"email": "safiraoktaviani@gmail.com",
"gender": "F"
},
@@ -79,7 +79,7 @@
"idPosition": "pos_staff_rt01",
"nik": "3201010101010008",
"name": "Agus Setiawan",
"phone": "081234567897",
"phone": "6281234567897",
"email": "agussetiawannn@gmail.com",
"gender": "M"
}

30
src/pages/_error.tsx Normal file
View File

@@ -0,0 +1,30 @@
import { NextPageContext } from "next";
function ErrorPage({ statusCode }: { statusCode?: number }) {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
backgroundColor: "#252A2F",
color: "white",
fontFamily: "Lato, sans-serif",
}}
>
<h1 style={{ fontSize: 24, fontWeight: 700 }}>
{statusCode === 404
? "404 - Halaman Tidak Ditemukan"
: "Terjadi Kesalahan"}
</h1>
</div>
);
}
ErrorPage.getInitialProps = ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default ErrorPage;